From 279f35495a122c9892198545f83e03c6fc50fa08 Mon Sep 17 00:00:00 2001 From: runge Date: Sun, 7 May 2006 00:50:13 +0000 Subject: x11vnc: support for video4linux webcams & tv-tuners, -24to32 bpp option, -rawfb console. --- x11vnc/v4l.c | 1689 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1689 insertions(+) create mode 100644 x11vnc/v4l.c (limited to 'x11vnc/v4l.c') diff --git a/x11vnc/v4l.c b/x11vnc/v4l.c new file mode 100644 index 0000000..9722f38 --- /dev/null +++ b/x11vnc/v4l.c @@ -0,0 +1,1689 @@ +/* -- v4l.c -- */ + +#include "x11vnc.h" +#include "cleanup.h" +#include "scan.h" +#include "xinerama.h" +#include "screen.h" + +#if LIBVNCSERVER_HAVE_LINUX_VIDEODEV_H +#if LIBVNCSERVER_HAVE_SYS_IOCTL_H +#include +#include +#define V4L_OK +#endif +#endif + +char *v4l_guess(char *str, int *fd); +void v4l_key_command(rfbBool down, rfbKeySym keysym, rfbClientPtr client); +void v4l_pointer_command(int mask, int x, int y, rfbClientPtr client); + +static int v4l1_val(int pct); +static int v4l1_width(int w); +static int v4l1_height(int h); +static int v4l1_resize(int fd, int w, int h); +static void v4l1_setfreq(int fd, unsigned long freq, int verb); +static void v4l1_set_input(int fd, int which); +static int v4l1_setfmt(int fd, char *fmt); +static void apply_settings(char *dev, char *settings, int *fd); +static int v4l1_dpct(int old, int d); +static void v4l_requery(void); +static void v4l_br(int b); +static void v4l_hu(int b); +static void v4l_co(int b); +static void v4l_cn(int b); +static void v4l_sz(int b); +static void v4l_sta(int sta); +static void v4l_inp(int inp); +static void v4l_fmt(char *fmt); +static int colon_n(char *line); +static char *colon_str(char *line); +static char *colon_tag(char *line); +static void lookup_rgb(char *g_fmt, int *g_b, int *mask_rev); +static char *v4l1_lu_palette(unsigned short palette); +static unsigned short v4l1_lu_palette_str(char *name, int *bits, int *rev); +static char *v4l2_lu_palette(unsigned int palette); +static unsigned int v4l2_lu_palette_str(char *name, int *bits, int *rev); +static int v4l1_query(int fd, int verbose); +static int v4l2_query(int fd, int verbose); +static int open_dev(char *dev); +static char *guess_via_v4l(char *dev, int *fd); +static char *guess_via_v4l_info(char *dev, int *fd); +static void parse_str(char *str, char **dev, char **settings, char **atparms); +static unsigned long lookup_freqtab(int sta); +static unsigned long lookup_freq(int sta); +static int lookup_station(unsigned long freq); +static void init_freqtab(char *file); +static void init_freqs(void); +static void init_ntsc_cable(void); + +#define C_VIDEO_CAPTURE 1 +#define C_PICTURE 2 +#define C_WINDOW 3 + +#ifdef V4L_OK +static struct video_capability v4l1_capability; +static struct video_channel v4l1_channel; +static struct video_tuner v4l1_tuner; +static struct video_picture v4l1_picture; +static struct video_window v4l1_window; + +#if HAVE_V4L2 +static struct v4l2_capability v4l2_capability; +static struct v4l2_input v4l2_input; +static struct v4l2_tuner v4l2_tuner; +static struct v4l2_fmtdesc v4l2_fmtdesc; +static struct v4l2_format v4l2_format; +/*static struct v4l2_framebuffer v4l2_fbuf; */ +/*static struct v4l2_queryctrl v4l2_qctrl; */ +#endif +#endif + +static int v4l1_cap = -1; +static int v4l2_cap = -1; +#define V4L1_MAX 65535 + +#define CHANNEL_MAX 500 +static unsigned long ntsc_cable[CHANNEL_MAX]; +static unsigned long custom_freq[CHANNEL_MAX]; + +static unsigned long last_freq = 0; +static int last_channel = 0; + +static int v4l1_val(int pct) { + /* pct is % */ + int val, max = V4L1_MAX; + if (pct < 0) { + return 0; + } else if (pct > 100) { + return max; + } + val = (pct * max)/100; + + return val; +} +static int v4l1_width(int w) { +#ifdef V4L_OK + int min = v4l1_capability.minwidth; + int max = v4l1_capability.maxwidth; + if (w < min) { + w = min; + } + if (w > max) { + w = max; + } +#endif + return w; +} +static int v4l1_height(int h) { +#ifdef V4L_OK + int min = v4l1_capability.minheight; + int max = v4l1_capability.maxheight; + if (h < min) { + h = min; + } + if (h > max) { + h = max; + } +#endif + return h; +} + +static int v4l1_resize(int fd, int w, int h) { + int dowin = 0; + +#ifdef V4L_OK + memset(&v4l1_window, 0, sizeof(v4l1_window)); + if (ioctl(fd, VIDIOCGWIN, &v4l1_window) == -1) { + return 0; + } + + if (w > 0) w = v4l1_width(w); + + if (w > 0 && w != (int) v4l1_window.width) { + dowin = 1; + } + + if (h > 0) h = v4l1_height(h); + + if (h > 0 && h != (int) v4l1_window.height) { + dowin = 1; + } + + if (dowin) { + v4l1_window.x = 0; + v4l1_window.y = 0; + ioctl(fd, VIDIOCSWIN, &v4l1_window); + if (w > 0) v4l1_window.width = w; + if (h > 0) v4l1_window.height = h; + fprintf(stderr, "calling V4L_1: VIDIOCSWIN\n"); + fprintf(stderr, "trying new size %dx%d\n", + v4l1_window.width, v4l1_window.height); + if (ioctl(fd, VIDIOCSWIN, &v4l1_window) == -1) { + perror("ioctl VIDIOCSWIN"); + return 0; + } + } +#endif + return 1; +} + +static void v4l1_setfreq(int fd, unsigned long freq, int verb) { +#ifdef V4L_OK + unsigned long f0, f1; + f1 = (freq * 16) / 1000; + ioctl(fd, VIDIOCGFREQ, &f0); + if (verb) fprintf(stderr, "read freq: %d\n", (int) f0); + if (freq > 0) { + if (ioctl(fd, VIDIOCSFREQ, &f1) == -1) { + perror("ioctl VIDIOCSFREQ"); + } else { + ioctl(fd, VIDIOCGFREQ, &f0); + if (verb) fprintf(stderr, "read freq: %d\n", (int) f0); + last_freq = freq; + } + } +#endif +} + +static void v4l1_set_input(int fd, int which) { +#ifdef V4L_OK + if (which != -1) { + memset(&v4l1_channel, 0, sizeof(v4l1_channel)); + v4l1_channel.channel = which; + if (ioctl(fd, VIDIOCGCHAN, &v4l1_channel) != -1) { + v4l1_channel.channel = which; + fprintf(stderr, "setting input channel to %d: %s\n", + which, v4l1_channel.name); + last_channel = which; + ioctl(fd, VIDIOCSCHAN, &v4l1_channel); + } + } +#endif +} + +static int v4l1_setfmt(int fd, char *fmt) { +#ifdef V4L_OK + unsigned short fnew; + int bnew, rnew; + + fnew = v4l1_lu_palette_str(fmt, &bnew, &rnew); + if (fnew) { + v4l1_picture.depth = bnew; + v4l1_picture.palette = fnew; + } + fprintf(stderr, "calling V4L_1: VIDIOCSPICT\n"); + if (ioctl(fd, VIDIOCSPICT, &v4l1_picture) == -1) { + perror("ioctl VIDIOCSPICT"); + return 0; + } + if (raw_fb_pixfmt) { + free(raw_fb_pixfmt); + } + raw_fb_pixfmt = strdup(fmt); +#endif + return 1; +} + +static int ignore_all = 0; + +static void apply_settings(char *dev, char *settings, int *fd) { + char *str, *p, *fmt = NULL, *tun = NULL, *inp = NULL; + int br = -1, co = -1, cn = -1, hu = -1; + int w = -1, h = -1, b = -1; + int sta = -1; + int setcnt = 0; +#ifdef V4L_OK + if (! settings || settings[0] == '\0') { + return; + } + str = strdup(settings); + p = strtok(str, ","); + while (p) { + if (strstr(p, "br=") == p) { + br = atoi(p+3); + if (br >= 0) setcnt++; + } else if (strstr(p, "co=") == p) { + co = atoi(p+3); + if (co >= 0) setcnt++; + } else if (strstr(p, "cn=") == p) { + cn = atoi(p+3); + if (cn >= 0) setcnt++; + } else if (strstr(p, "hu=") == p) { + hu = atoi(p+3); + if (hu >= 0) setcnt++; + } else if (strstr(p, "w=") == p) { + w = atoi(p+2); + if (w > 0) setcnt++; + } else if (strstr(p, "h=") == p) { + h = atoi(p+2); + if (h > 0) setcnt++; + } else if (strstr(p, "bpp=") == p) { + b = atoi(p+4); + if (b > 0) setcnt++; + } else if (strstr(p, "fmt=") == p) { + fmt = strdup(p+4); + setcnt++; + } else if (strstr(p, "tun=") == p) { + tun = strdup(p+4); + setcnt++; + } else if (strstr(p, "inp=") == p) { + inp = strdup(p+4); + setcnt++; + } else if (strstr(p, "sta=") == p) { + sta = atoi(p+4); + setcnt++; + } + p = strtok(NULL, ","); + } + free(str); + if (! setcnt) { + return; + } + if (*fd < 0) { + *fd = open_dev(dev); + } + if (*fd < 0) { + return; + } + v4l1_cap = v4l1_query(*fd, 1); + v4l2_cap = v4l2_query(*fd, 1); + + if (v4l1_cap && ! ignore_all) { + if (br >= 0) v4l1_picture.brightness = v4l1_val(br); + if (hu >= 0) v4l1_picture.hue = v4l1_val(hu); + if (co >= 0) v4l1_picture.colour = v4l1_val(co); + if (cn >= 0) v4l1_picture.contrast = v4l1_val(cn); + + fprintf(stderr, "calling V4L_1: VIDIOCSPICT\n"); + if (ioctl(*fd, VIDIOCSPICT, &v4l1_picture) == -1) { + perror("ioctl VIDIOCSPICT"); + } + + if (fmt) { + v4l1_setfmt(*fd, fmt); + } else if (b > 0 && b != v4l1_picture.depth) { + if (b == 8) { + v4l1_setfmt(*fd, "HI240"); + } else if (b == 16) { + v4l1_setfmt(*fd, "RGB565"); + } else if (b == 24) { + v4l1_setfmt(*fd, "RGB24"); + } else if (b == 32) { + v4l1_setfmt(*fd, "RGB32"); + } + } + + v4l1_resize(*fd, w, h); + + if (tun) { + int mode = -1; + if (!strcasecmp(tun, "PAL")) { + mode = VIDEO_MODE_PAL; + } else if (!strcasecmp(tun, "NTSC")) { + mode = VIDEO_MODE_NTSC; + } else if (!strcasecmp(tun, "SECAM")) { + mode = VIDEO_MODE_SECAM; + } else if (!strcasecmp(tun, "AUTO")) { + mode = VIDEO_MODE_AUTO; + } + if (mode != -1) { + int i; + for (i=0; i< v4l1_capability.channels; i++) { + memset(&v4l1_channel, 0, sizeof(v4l1_channel)); + v4l1_channel.channel = i; + if (ioctl(*fd, VIDIOCGCHAN, &v4l1_channel) == -1) { + continue; + } + if (! v4l1_channel.tuners) { + continue; + } + if (v4l1_channel.norm == mode) { + continue; + } + v4l1_channel.norm = mode; + ioctl(*fd, VIDIOCSCHAN, &v4l1_channel); + } + } + } + if (inp) { + char s[2]; + int i, chan = -1; + + s[0] = inp[0]; + s[1] = '\0'; + if (strstr("0123456789", s)) { + chan = atoi(inp); + } else { + for (i=0; i< v4l1_capability.channels; i++) { + memset(&v4l1_channel, 0, sizeof(v4l1_channel)); + v4l1_channel.channel = i; + if (ioctl(*fd, VIDIOCGCHAN, &v4l1_channel) == -1) { + continue; + } + if (!strcmp(v4l1_channel.name, inp)) { + chan = i; + break; + } + } + } + v4l1_set_input(*fd, chan); + } + if (sta >= 0) { + unsigned long freq = lookup_freq(sta); + v4l1_setfreq(*fd, freq, 1); + } + } + v4l1_cap = v4l1_query(*fd, 1); + v4l2_cap = v4l2_query(*fd, 1); +#else + return; +#endif +} + +static double dval = 0.05; + +static int v4l1_dpct(int old, int d) { + int new, max = V4L1_MAX; + + /* -1 and 1 are special cases for "small increments" */ + if (d == -1) { + new = old - (int) (dval * max); + } else if (d == 1) { + new = old + (int) (dval * max); + } else { + new = (d * max)/100; + } + if (new < 0) { + new = 0; + } + if (new > max) { + new = max; + } + return new; +} + +static void v4l_requery(void) { + if (raw_fb_fd < 0) { + return; + } + v4l1_cap = v4l1_query(raw_fb_fd, 1); + v4l2_cap = v4l2_query(raw_fb_fd, 1); +} + +static void v4l_br(int b) { +#ifdef V4L_OK + int old = v4l1_picture.brightness; + + v4l1_picture.brightness = v4l1_dpct(old, b); + ioctl(raw_fb_fd, VIDIOCSPICT, &v4l1_picture); + v4l_requery(); +#endif +} + +static void v4l_hu(int b) { +#ifdef V4L_OK + int old = v4l1_picture.hue; + + v4l1_picture.hue = v4l1_dpct(old, b); + ioctl(raw_fb_fd, VIDIOCSPICT, &v4l1_picture); + v4l_requery(); +#endif +} + +static void v4l_co(int b) { +#ifdef V4L_OK + int old = v4l1_picture.colour; + + v4l1_picture.colour = v4l1_dpct(old, b); + ioctl(raw_fb_fd, VIDIOCSPICT, &v4l1_picture); + v4l_requery(); +#endif +} + +static void v4l_cn(int b) { +#ifdef V4L_OK + int old = v4l1_picture.contrast; + + v4l1_picture.contrast = v4l1_dpct(old, b); + ioctl(raw_fb_fd, VIDIOCSPICT, &v4l1_picture); + v4l_requery(); +#endif +} + +static void v4l_sz(int b) { +#ifdef V4L_OK + int w_old = v4l1_window.width; + int h_old = v4l1_window.height; + int w, h; + + if (w_old == 0) { + w_old = 160; + } + if (h_old == 0) { + h_old = 120; + } + + if (b == 1) { + w = w_old + (int) (0.15 * w_old); + h = h_old + (int) (0.15 * h_old); + } else if (b == -1) { + w = w_old - (int) (0.15 * w_old); + h = h_old - (int) (0.15 * h_old); + } else { + return; + } + + if (! v4l1_resize(raw_fb_fd, w, h)) { + return; + } + + v4l_requery(); + + push_black_screen(4); + + ignore_all = 1; + do_new_fb(1); + ignore_all = 0; +#endif +} + +static void v4l_sta(int sta) { +#ifdef V4L_OK + unsigned long freq = 0; + int cur = lookup_station(last_freq); + + if (! last_freq) { + if (sta == 0 || sta == -1) { + sta = 11; + } + } + + if (sta == -1) { + while (cur > 0) { + freq = lookup_freq(--cur); + if (freq) { + break; + } + } + } else if (sta == 0) { + while (cur < CHANNEL_MAX - 1) { + freq = lookup_freq(++cur); + if (freq) { + break; + } + } + } else { + freq = lookup_freq(sta); + cur = sta; + } + fprintf(stderr, "to station %d / %d\n", cur, (int) freq); + v4l1_setfreq(raw_fb_fd, freq, 0); +#endif +} + +static void v4l_inp(int inp) { +#ifdef V4L_OK + int next = -1; + if (inp == -1) { + inp = last_channel + 1; + if (inp >= v4l1_capability.channels) { + inp = 0; + } + next = inp; + } else if (inp == -2) { + inp = last_channel - 1; + if (inp < 0) { + inp = v4l1_capability.channels - 1; + } + next = inp; + } else { + next = inp; + } + v4l1_set_input(raw_fb_fd, next); +#endif +} + +static void v4l_fmt(char *fmt) { + if (v4l1_setfmt(raw_fb_fd, fmt)) { + v4l_requery(); + + ignore_all = 1; + do_new_fb(1); + ignore_all = 0; + } +} + +void v4l_key_command(rfbBool down, rfbKeySym keysym, rfbClientPtr client) { + if (raw_fb_fd < 0) { + return; + } + if (! down) { + return; + } + if (keysym == XK_b) { + v4l_br(-1); + } else if (keysym == XK_B) { + v4l_br(+1); + } else if (keysym == XK_h) { + v4l_hu(-1); + } else if (keysym == XK_H) { + v4l_hu(+1); + } else if (keysym == XK_c) { + v4l_co(-1); + } else if (keysym == XK_C) { + v4l_co(+1); + } else if (keysym == XK_n) { + v4l_cn(-1); + } else if (keysym == XK_N) { + v4l_cn(+1); + } else if (keysym == XK_s) { + v4l_sz(-1); + } else if (keysym == XK_S) { + v4l_sz(+1); + } else if (keysym == XK_i) { + v4l_inp(-1); + } else if (keysym == XK_I) { + v4l_inp(-2); + } else if (keysym == XK_Up) { + v4l_sta(+0); + } else if (keysym == XK_Down) { + v4l_sta(-1); + } else if (keysym == XK_F1) { + v4l_fmt("HI240"); + } else if (keysym == XK_F2) { + v4l_fmt("RGB565"); + } else if (keysym == XK_F3) { + v4l_fmt("RGB24"); + } else if (keysym == XK_F4) { + v4l_fmt("RGB32"); + } else if (keysym == XK_F5) { + v4l_fmt("RGB555"); + } else if (keysym == XK_F6) { + v4l_fmt("GREY"); + } + if (client) {} +} + + +void v4l_pointer_command(int mask, int x, int y, rfbClientPtr client) { + if (mask || x || y || client) {} +} + +static int colon_n(char *line) { + char *q; + int n; + q = strrchr(line, ':'); + if (! q) { + return 0; + } + q = lblanks(q+1); + if (sscanf(q, "%d", &n) == 1) { + return n; + } + return 0; +} + +static char *colon_str(char *line) { + char *q, *p, *t; + q = strrchr(line, ':'); + if (! q) { + return strdup(""); + } + q = lblanks(q+1); + p = strpbrk(q, " \t\n"); + if (p) { + *p = '\0'; + } + t = strdup(q); + *p = '\n'; + return t; +} + +static char *colon_tag(char *line) { + char *q, *p, *t; + q = strrchr(line, '['); + if (! q) { + return strdup(""); + } + q++; + p = strrchr(q, ']'); + if (! p) { + return strdup(""); + } + *p = '\0'; + t = strdup(q); + *p = ']'; + return t; +} + +static void lookup_rgb(char *fmt, int *bits, int *rev) { + int tb, tr; + + if (v4l2_lu_palette_str(fmt, &tb, &tr)) { + *bits = tb; + *rev = tr; + return; + } + if (v4l1_lu_palette_str(fmt, &tb, &tr)) { + *bits = tb; + *rev = tr; + return; + } +} + +static char *v4l1_lu_palette(unsigned short palette) { + switch(palette) { +#ifdef V4L_OK + case VIDEO_PALETTE_GREY: return "GREY"; + case VIDEO_PALETTE_HI240: return "HI240"; + case VIDEO_PALETTE_RGB565: return "RGB565"; + case VIDEO_PALETTE_RGB24: return "RGB24"; + case VIDEO_PALETTE_RGB32: return "RGB32"; + case VIDEO_PALETTE_RGB555: return "RGB555"; + case VIDEO_PALETTE_YUV422: return "YUV422"; + case VIDEO_PALETTE_YUYV: return "YUYV"; + case VIDEO_PALETTE_UYVY: return "UYVY"; + case VIDEO_PALETTE_YUV420: return "YUV420"; + case VIDEO_PALETTE_YUV411: return "YUV411"; + case VIDEO_PALETTE_RAW: return "RAW"; + case VIDEO_PALETTE_YUV422P: return "YUV422P"; + case VIDEO_PALETTE_YUV411P: return "YUV411P"; + case VIDEO_PALETTE_YUV420P: return "YUV420P"; + case VIDEO_PALETTE_YUV410P: return "YUV410P"; +#endif + default: return "unknown"; + } +} + +static unsigned short v4l1_lu_palette_str(char *name, int *bits, int *rev) { +#ifdef V4L_OK + *rev = 0; + if (!strcmp(name, "RGB555")) { + *bits = 16; + return VIDEO_PALETTE_RGB555; + } else if (!strcmp(name, "RGB565")) { + *bits = 16; + return VIDEO_PALETTE_RGB565; + } else if (!strcmp(name, "RGB24")) { + *bits = 24; + return VIDEO_PALETTE_RGB24; + } else if (!strcmp(name, "RGB32")) { + *bits = 32; + return VIDEO_PALETTE_RGB32; + } else if (!strcmp(name, "HI240")) { + *bits = 8; + return VIDEO_PALETTE_HI240; + } else if (!strcmp(name, "GREY")) { + *bits = 8; + return VIDEO_PALETTE_GREY; + } +#endif + return 0; +} + +static char *v4l2_lu_palette(unsigned int fmt) { + switch(fmt) { +#if defined(V4L_OK) && HAVE_V4L2 + case V4L2_PIX_FMT_RGB332: return "RGB332"; + case V4L2_PIX_FMT_RGB555: return "RGB555"; + case V4L2_PIX_FMT_RGB565: return "RGB565"; + case V4L2_PIX_FMT_RGB555X: return "RGB555X"; + case V4L2_PIX_FMT_RGB565X: return "RGB565X"; + case V4L2_PIX_FMT_BGR24: return "BGR24"; + case V4L2_PIX_FMT_RGB24: return "RGB24"; + case V4L2_PIX_FMT_BGR32: return "BGR32"; + case V4L2_PIX_FMT_RGB32: return "RGB32"; + case V4L2_PIX_FMT_GREY: return "GREY"; + case V4L2_PIX_FMT_YVU410: return "YVU410"; + case V4L2_PIX_FMT_YVU420: return "YVU420"; + case V4L2_PIX_FMT_YUYV: return "YUYV"; + case V4L2_PIX_FMT_UYVY: return "UYVY"; + case V4L2_PIX_FMT_YUV422P: return "YUV422P"; + case V4L2_PIX_FMT_YUV411P: return "YUV411P"; + case V4L2_PIX_FMT_Y41P: return "Y41P"; + case V4L2_PIX_FMT_NV12: return "NV12"; + case V4L2_PIX_FMT_NV21: return "NV21"; + case V4L2_PIX_FMT_YUV410: return "YUV410"; + case V4L2_PIX_FMT_YUV420: return "YUV420"; + case V4L2_PIX_FMT_YYUV: return "YYUV"; + case V4L2_PIX_FMT_HI240: return "HI240"; + case V4L2_PIX_FMT_MJPEG: return "MJPEG"; + case V4L2_PIX_FMT_JPEG: return "JPEG"; + case V4L2_PIX_FMT_DV: return "DV"; + case V4L2_PIX_FMT_MPEG: return "MPEG"; +#endif + default: return "unknown"; + } +} + +static unsigned int v4l2_lu_palette_str(char *name, int *bits, int *rev) { +#if defined(V4L_OK) && HAVE_V4L2 + if (!strcmp(name, "RGB1") || !strcmp(name, "RGB332")) { + *bits = 8; + *rev = 0; + return V4L2_PIX_FMT_RGB332; + } else if (!strcmp(name, "RGBO") || !strcmp(name, "RGB555")) { + *bits = 16; + *rev = 0; + return V4L2_PIX_FMT_RGB555; + } else if (!strcmp(name, "RGBP") || !strcmp(name, "RGB565")) { + *bits = 16; + *rev = 0; + return V4L2_PIX_FMT_RGB565; + } else if (!strcmp(name, "RGBQ") || !strcmp(name, "RGB555X")) { + *bits = 16; + *rev = 1; + return V4L2_PIX_FMT_RGB555X; + } else if (!strcmp(name, "RGBR") || !strcmp(name, "RGB565X")) { + *bits = 16; + *rev = 1; + return V4L2_PIX_FMT_RGB565X; + } else if (!strcmp(name, "BGR3") || !strcmp(name, "BGR24")) { + *bits = 24; + *rev = 1; + return V4L2_PIX_FMT_BGR24; + } else if (!strcmp(name, "RGB3") || !strcmp(name, "RGB24")) { + *bits = 24; + *rev = 0; + return V4L2_PIX_FMT_RGB24; + } else if (!strcmp(name, "BGR4") || !strcmp(name, "BGR32")) { + *bits = 32; + *rev = 1; + return V4L2_PIX_FMT_BGR32; + } else if (!strcmp(name, "RGB4") || !strcmp(name, "RGB32")) { + *bits = 32; + *rev = 0; + return V4L2_PIX_FMT_RGB32; + } else if (!strcmp(name, "GREY")) { + *bits = 8; + *rev = 0; + return V4L2_PIX_FMT_GREY; + } +#endif + return 0; +} + +static int v4l1_query(int fd, int v) { +#ifdef V4L_OK + unsigned int i; + + memset(&v4l1_capability, 0, sizeof(v4l1_capability)); + memset(&v4l1_channel, 0, sizeof(v4l1_channel)); + memset(&v4l1_tuner, 0, sizeof(v4l1_tuner)); + memset(&v4l1_picture, 0, sizeof(v4l1_picture)); + memset(&v4l1_window, 0, sizeof(v4l1_window)); + + if (v) fprintf(stderr, "\nV4L_1 query:\n"); +#ifdef VIDIOCGCAP + if (ioctl(fd, VIDIOCGCAP, &v4l1_capability) == -1) { + perror("ioctl VIDIOCGCAP"); + fprintf(stderr, "\n"); + return 0; + } +#else + return 0; +#endif + if (v) fprintf(stderr, "v4l-1 capability:\n"); + if (v) fprintf(stderr, " name: %s\n", v4l1_capability.name); + if (v) fprintf(stderr, " channels: %d\n", v4l1_capability.channels); + if (v) fprintf(stderr, " audios: %d\n", v4l1_capability.audios); + if (v) fprintf(stderr, " maxwidth: %d\n", v4l1_capability.maxwidth); + if (v) fprintf(stderr, " maxheight: %d\n", v4l1_capability.maxheight); + if (v) fprintf(stderr, " minwidth: %d\n", v4l1_capability.minwidth); + if (v) fprintf(stderr, " minheight: %d\n", v4l1_capability.minheight); + + for (i=0; (int) i < v4l1_capability.channels; i++) { + char *type = "unknown"; + memset(&v4l1_channel, 0, sizeof(v4l1_channel)); + v4l1_channel.channel = i; + if (ioctl(fd, VIDIOCGCHAN, &v4l1_channel) == -1) { + perror("ioctl VIDIOCGCHAN"); + continue; + } + if (v4l1_channel.type == VIDEO_TYPE_TV) { + type = "TV"; + } else if (v4l1_channel.type == VIDEO_TYPE_CAMERA) { + type = "CAMERA"; + } + if (v) fprintf(stderr, " channel[%d]: %s\ttuners: %d norm: %d type: %d %s\n", + i, v4l1_channel.name, v4l1_channel.tuners, v4l1_channel.norm, + v4l1_channel.type, type); + } + + memset(&v4l1_tuner, 0, sizeof(v4l1_tuner)); + if (ioctl(fd, VIDIOCGTUNER, &v4l1_tuner) != -1) { + char *mode = "unknown"; + if (v4l1_tuner.mode == VIDEO_MODE_PAL) { + mode = "PAL"; + } else if (v4l1_tuner.mode == VIDEO_MODE_NTSC) { + mode = "NTSC"; + } else if (v4l1_tuner.mode == VIDEO_MODE_SECAM) { + mode = "SECAM"; + } else if (v4l1_tuner.mode == VIDEO_MODE_AUTO) { + mode = "AUTO"; + } + + if (v) fprintf(stderr, " tuner[%d]: %s\tflags: 0x%x mode: %s\n", + v4l1_tuner.tuner, v4l1_tuner.name, v4l1_tuner.flags, mode); + + } + + if (ioctl(fd, VIDIOCGPICT, &v4l1_picture) == -1) { + perror("ioctl VIDIOCGCHAN"); + return 0; + } + if (v) fprintf(stderr, "v4l-1 picture:\n"); + if (v) fprintf(stderr, " brightness: %d\n", v4l1_picture.brightness); + if (v) fprintf(stderr, " hue: %d\n", v4l1_picture.hue); + if (v) fprintf(stderr, " colour: %d\n", v4l1_picture.colour); + if (v) fprintf(stderr, " contrast: %d\n", v4l1_picture.contrast); + if (v) fprintf(stderr, " whiteness: %d\n", v4l1_picture.whiteness); + if (v) fprintf(stderr, " depth: %d\n", v4l1_picture.depth); + if (v) fprintf(stderr, " palette: %d %s\n", v4l1_picture.palette, + v4l1_lu_palette(v4l1_picture.palette)); + + if (ioctl(fd, VIDIOCGWIN, &v4l1_window) == -1) { + perror("ioctl VIDIOCGWIN"); + if (v) fprintf(stderr, "\n"); + return 0; + } + if (v) fprintf(stderr, "v4l-1 window:\n"); + if (v) fprintf(stderr, " x: %d\n", v4l1_window.x); + if (v) fprintf(stderr, " y: %d\n", v4l1_window.y); + if (v) fprintf(stderr, " width: %d\n", v4l1_window.width); + if (v) fprintf(stderr, " height: %d\n", v4l1_window.height); + if (v) fprintf(stderr, " chromakey: %d\n", v4l1_window.chromakey); + if (v) fprintf(stderr, "\n"); + + return 1; +#else + return 0; +#endif /* V4L_OK */ + +} +static int v4l2_query(int fd, int v) { +#if defined(V4L_OK) && HAVE_V4L2 + unsigned int i; + + memset(&v4l2_capability, 0, sizeof(v4l2_capability)); + memset(&v4l2_input, 0, sizeof(v4l2_input)); + memset(&v4l2_tuner, 0, sizeof(v4l2_tuner)); + memset(&v4l2_fmtdesc, 0, sizeof(v4l2_fmtdesc)); + memset(&v4l2_format, 0, sizeof(v4l2_format)); + + if (v) fprintf(stderr, "\nV4L_2 query:\n"); +#ifdef VIDIOC_QUERYCAP + if (ioctl(fd, VIDIOC_QUERYCAP, &v4l2_capability) == -1) { + perror("ioctl VIDIOC_QUERYCAP"); + if (v) fprintf(stderr, "\n"); + return 0; + } +#else + return 0; +#endif + + if (v) fprintf(stderr, "v4l-2 capability:\n"); + if (v) fprintf(stderr, " driver: %s\n", v4l2_capability.driver); + if (v) fprintf(stderr, " card: %s\n", v4l2_capability.card); + if (v) fprintf(stderr, " bus_info: %s\n", v4l2_capability.bus_info); + if (v) fprintf(stderr, " version: %d\n", v4l2_capability.version); + if (v) fprintf(stderr, " capabilities: %u\n", v4l2_capability.capabilities); + + for (i=0; ; i++) { + memset(&v4l2_input, 0, sizeof(v4l2_input)); + v4l2_input.index = i; + if (ioctl(fd, VIDIOC_ENUMINPUT, &v4l2_input) == -1) { + break; + } + if (v) fprintf(stderr, " input[%d]: %s\ttype: %d tuner: %d\n", + i, v4l2_input.name, v4l2_input.type, v4l2_input.tuner); + } + if (v4l2_capability.capabilities & V4L2_CAP_TUNER) { + for (i=0; ; i++) { + memset(&v4l2_tuner, 0, sizeof(v4l2_tuner)); + v4l2_tuner.index = i; + if (ioctl(fd, VIDIOC_G_TUNER, &v4l2_tuner) == -1) { + break; + } + if (v) fprintf(stderr, " tuner[%d]: %s\ttype: %d\n", + i, v4l2_tuner.name, v4l2_tuner.type); + } + } + if (v4l2_capability.capabilities & V4L2_CAP_VIDEO_CAPTURE) { + for (i=0; ; i++) { + memset(&v4l2_fmtdesc, 0, sizeof(v4l2_fmtdesc)); + v4l2_fmtdesc.index = i; + v4l2_fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (ioctl(fd, VIDIOC_ENUM_FMT, &v4l2_fmtdesc) == -1) { + break; + } + if (v) fprintf(stderr, " fmtdesc[%d]: %s\ttype: %d" + " pixelformat: %d\n", + i, v4l2_fmtdesc.description, v4l2_fmtdesc.type, + v4l2_fmtdesc.pixelformat); + } + v4l2_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(fd, VIDIOC_G_FMT, &v4l2_format) == -1) { + perror("ioctl VIDIOC_G_FMT"); + } else { + if (v) fprintf(stderr, " width: %d\n", v4l2_format.fmt.pix.width); + if (v) fprintf(stderr, " height: %d\n", v4l2_format.fmt.pix.height); + if (v) fprintf(stderr, " format: %u %s\n", + v4l2_format.fmt.pix.pixelformat, + v4l2_lu_palette(v4l2_format.fmt.pix.pixelformat)); + } + } + + return 1; +#else + return 0; +#endif /* V4L_OK && HAVE_V4L2 */ + +} + +static int open_dev(char *dev) { + int dfd = -1; + if (! dev) { + return dfd; + } + dfd = open(dev, O_RDWR); + if (dfd < 0) { + rfbLog("failed to rawfb file: %s O_RDWR\n", dev); + rfbLogPerror("open"); + dfd = open(dev, O_RDONLY); + } + if (dfd < 0) { + rfbLog("failed to rawfb file: %s\n", dev); + rfbLog("failed to rawfb file: %s O_RDONLY\n", dev); + rfbLogPerror("open"); + } + return dfd; +} + +static char *guess_via_v4l(char *dev, int *fd) { +#ifdef V4L_OK + int dfd; + + if (*fd < 0) { + dfd = open_dev(dev); + *fd = dfd; + } + dfd = *fd; + if (dfd < 0) { + return NULL; + } + if (v4l1_cap < 0) { + v4l1_cap = v4l1_query(dfd, 1); + } + if (v4l2_cap < 0) { + v4l2_cap = v4l2_query(dfd, 1); + } + + if (v4l2_cap) { +#if HAVE_V4L2 + int g_w = v4l2_format.fmt.pix.width; + int g_h = v4l2_format.fmt.pix.height; + int g_d = 0, g_rev; + + if (v4l2_format.fmt.pix.pixelformat) { + char *str = v4l2_lu_palette(v4l2_format.fmt.pix.pixelformat); + if (strcmp(str, "unknown")) { + v4l2_lu_palette_str(str, &g_d, &g_rev); + } + } + + if (g_w > 0 && g_h > 0 && g_d > 0) { + char *atparms = (char *) malloc(200); + char *pal = v4l2_lu_palette(v4l2_format.fmt.pix.pixelformat); + sprintf(atparms, "%dx%dx%d", g_w, g_h, g_d); + if (strstr(pal, "RGB555")) { + strcat(atparms, ":7c00/3e0/1f"); + } + *fd = dfd; + return atparms; + } +#endif + } + if (v4l1_cap) { + int g_w = v4l1_window.width; + int g_h = v4l1_window.height; + int g_d = v4l1_picture.depth; + int g_rev; + if (g_d == 0) { + char *str = v4l1_lu_palette(v4l1_picture.palette); + if (strcmp(str, "unknown")) { + v4l1_lu_palette_str(str, &g_d, &g_rev); + } + } +if (0) fprintf(stderr, "v4l1: %d %d %d\n", g_w, g_h, g_d); + if (g_w > 0 && g_h > 0 && g_d > 0) { + char *atparms = (char *) malloc(200); + char *pal = v4l1_lu_palette(v4l1_picture.palette); + fprintf(stderr, "palette: %s\n", pal); + sprintf(atparms, "%dx%dx%d", g_w, g_h, g_d); + if (strstr(pal, "RGB555")) { + strcat(atparms, ":7c00/3e0/1f"); + } + *fd = dfd; + return atparms; + } + } + + /* failure */ + close(dfd); + return NULL; +#else + return NULL; +#endif +} + +static char *guess_via_v4l_info(char *dev, int *fd) { + char *atparms, *cmd; + char line[1024], tmp[] = "/tmp/x11vnc-tmp.XXXXXX"; + FILE *out; + int tmp_fd, len, rc, curr = 0; + int g_w = 0, g_h = 0, g_b = 0, mask_rev = 0; + char *g_fmt = NULL; + + if (*fd) {} + + if (no_external_cmds) { + rfbLog("guess_via_v4l_info: cannot run external " + "command: v4l-info\n"); + return NULL; + } + + if (strchr(dev, '\'')) { + rfbLog("guess_via_v4l_info: bad dev string: %s\n", dev); + return NULL; + } + + tmp_fd = mkstemp(tmp); + if (tmp_fd < 0) { + return NULL; + } + + len = strlen("v4l-info")+1+1+strlen(dev)+1+1+1+1+strlen(tmp)+1; + cmd = (char *) malloc(len); + rfbLog("guess_via_v4l_info running: v4l-info '%s'\n", dev); + sprintf(cmd, "v4l-info '%s' > %s", dev, tmp); + + close(tmp_fd); + rc = system(cmd); + if (rc != 0) { + unlink(tmp); + return NULL; + } + + out = fopen(tmp, "r"); + if (out == NULL) { + unlink(tmp); + return NULL; + } + + curr = 0; + while (fgets(line, 1024, out) != NULL) { + char *lb = lblanks(line); + if (strstr(line, "video capture") == line) { + curr = C_VIDEO_CAPTURE; + } else if (strstr(line, "picture") == line) { + curr = C_PICTURE; + } else if (strstr(line, "window") == line) { + curr = C_WINDOW; + } + +if (0) fprintf(stderr, "lb: %s", lb); + + if (curr == C_VIDEO_CAPTURE) { + if (strstr(lb, "pixelformat ") == lb) { + fprintf(stderr, "%s", line); + } else if (strstr(lb, "fmt.pix.width ") == lb) { + if (! g_w) { + g_w = colon_n(line); + } + } else if (strstr(lb, "fmt.pix.height ") == lb) { + if (! g_h) { + g_h = colon_n(line); + } + } else if (strstr(lb, "fmt.pix.pixelformat ") == lb) { + if (! g_fmt) { + g_fmt = colon_tag(line); + } + } + } else if (curr == C_PICTURE) { + if (strstr(lb, "depth ") == lb) { + if (! g_b) { + g_b = colon_n(line); + } + } else if (strstr(lb, "palette ") == lb) { + if (! g_fmt) { + g_fmt = colon_str(line); + } + } + } else if (curr == C_WINDOW) { + if (strstr(lb, "width ") == lb) { + if (! g_w) { + g_w = colon_n(line); + } + } else if (strstr(lb, "height ") == lb) { + if (! g_h) { + g_h = colon_n(line); + } + } + } + } + fclose(out); + unlink(tmp); + + if (! g_w) { + rfbLog("could not guess device width.\n"); + return NULL; + } + rfbLog("guessed device width: %d\n", g_w); + + if (! g_h) { + rfbLog("could not guess device height.\n"); + return NULL; + } + rfbLog("guessed device height: %d\n", g_h); + + if (g_fmt) { + rfbLog("guessed pixel fmt: %s\n", g_fmt); + lookup_rgb(g_fmt, &g_b, &mask_rev); + } + if (! g_b) { + rfbLog("could not guess device bpp.\n"); + return NULL; + } + rfbLog("guessed device bpp: %d\n", g_b); + + atparms = (char *) malloc(100); + sprintf(atparms, "%dx%dx%d", g_w, g_h, g_b); + return atparms; +} + +static void parse_str(char *str, char **dev, char **settings, char **atparms) { + char *p, *q, *s = NULL; + + q = strchr(str, '@'); + if (q && strlen(q+1) > 0) { + /* ends @WxHXB... */ + *atparms = strdup(q+1); + *q = '\0'; + } + + q = strchr(str, ':'); + if (q && strlen(q+1) > 0) { + /* ends :br=N,w=N... */ + s = strdup(q+1); + *settings = s; + *q = '\0'; + } + + if (s != NULL) { + /* see if fn=filename */ + q = strstr(s, "fn="); + if (q) { + q += strlen("fn="); + p = strchr(q, ','); + if (p) { + *p = '\0'; + *dev = strdup(q); + *p = ','; + } else { + *dev = strdup(q); + } + rfbLog("set video device to: '%s'\n", *dev); + } + } + + if (*dev == NULL) { + s = (char *) malloc(strlen("/dev/") + strlen(str) + 1); + if (strstr(str, "/dev/") == str) { + sprintf(s, "%s", str); + } else { + sprintf(s, "/dev/%s", str); + } + *dev = s; + rfbLog("set video device to: '%s'\n", *dev); + } +} + +char *v4l_guess(char *str, int *fd) { + char *dev = NULL, *settings = NULL, *atparms = NULL; + + parse_str(str, &dev, &settings, &atparms); + + init_freqs(); + + v4l1_cap = -1; + v4l2_cap = -1; + *fd = -1; + + if (dev == NULL) { + rfbLog("v4l_guess: could not find device in: %s\n", str); + return NULL; + } + + if (settings) { + apply_settings(dev, settings, fd); + } + + if (atparms) { + /* use user's parameters. */ + char *t = (char *) malloc(5+strlen(dev)+1+strlen(atparms)+1); + sprintf(t, "snap:%s@%s", dev, atparms); + return t; + } + + /* try to query the device for parameters. */ + atparms = guess_via_v4l(dev, fd); + if (atparms == NULL) { + /* try again with v4l-info(1) */ + atparms = guess_via_v4l_info(dev, fd); + } + + if (atparms == NULL) { + /* bad news */ + if (*fd >= 0) { + close(*fd); + } + *fd = -1; + return NULL; + } else { + char *t = (char *) malloc(5+strlen(dev)+1+strlen(atparms)+1); + sprintf(t, "snap:%s@%s", dev, atparms); + return t; + } +} + +static unsigned long lookup_freqtab(int sta) { + + if (sta >= CHANNEL_MAX) { + return (unsigned long) sta; + } + if (sta < 0 || sta >= CHANNEL_MAX) { + return 0; + } + return custom_freq[sta]; +} + +static unsigned long lookup_freq(int sta) { + if (freqtab) { + return lookup_freqtab(sta); + } + if (sta >= CHANNEL_MAX) { + return (unsigned long) sta; + } + if (sta < 1 || sta > 125) { + return 0; + } + return ntsc_cable[sta]; +} + +static int lookup_station(unsigned long freq) { + int i; + if (freqtab) { + for (i = 0; i < CHANNEL_MAX; i++) { +if (0) fprintf(stderr, "%lu %lu\n", freq, custom_freq[i]); + if (freq == custom_freq[i]) { + return i; + } + } + } else { + for (i = 1; i <= 125; i++) { + if (freq == ntsc_cable[i]) { + return i; + } + } + } + return 0; +} + +static void init_freqtab(char *file) { + char *p, *q, *dir, *file2; + char line[1024], inc[1024]; + char *text, *str; + int size = 0, maxn, extra, currn; + FILE *in1, *in2; + static int v = 1; + if (quiet) { + v = 0; + } + + /* YUCK */ + + dir = strdup(file); + q = strrchr(dir, '/'); + if (q) { + *(q+1) = '\0'; + } else { + free(dir); + dir = strdup("./"); + } + file2 = (char *) malloc(strlen(dir) + 1024 + 1); + in1 = fopen(file, "r"); + if (in1 == NULL) { + rfbLog("error opening freqtab: %s\n", file); + clean_up_exit(1); + } + if (v) fprintf(stderr, "loading frequencies from: %s\n", file); + while (fgets(line, 1024, in1) != NULL) { + char *lb; + char line2[1024]; + size += strlen(line); + lb = lblanks(line); + if (strstr(lb, "#include") == lb && + sscanf(lb, "#include %s", inc) == 1) { + char *q, *s = inc; + if (s[0] == '"') { + s++; + } + q = strrchr(s, '"'); + if (q) { + *q = '\0'; + } + sprintf(file2, "%s%s", dir, s); + in2 = fopen(file2, "r"); + if (in2 == NULL) { + rfbLog("error opening freqtab include: %s %s\n", line, file2); + clean_up_exit(1); + } + if (v) fprintf(stderr, "loading frequencies from: %s\n", file2); + while (fgets(line2, 1024, in2) != NULL) { + size += strlen(line2); + } + fclose(in2); + } + } + fclose(in1); + + size = 4*(size + 10000); + + text = (char *) malloc(size); + + text[0] = '\0'; + + in1 = fopen(file, "r"); + if (in1 == NULL) { + rfbLog("error opening freqtab: %s\n", file); + clean_up_exit(1); + } + while (fgets(line, 1024, in1) != NULL) { + char *lb; + char line2[1024]; + lb = lblanks(line); + if (lb[0] == '[') { + strcat(text, lb); + } else if (strstr(lb, "freq")) { + strcat(text, lb); + } else if (strstr(lb, "#include") == lb && + sscanf(lb, "#include %s", inc) == 1) { + char *lb2; + char *q, *s = inc; + if (s[0] == '"') { + s++; + } + q = strrchr(s, '"'); + if (q) { + *q = '\0'; + } + sprintf(file2, "%s%s", dir, s); + in2 = fopen(file2, "r"); + if (in2 == NULL) { + rfbLog("error opening freqtab include: %s %s\n", line, file2); + clean_up_exit(1); + } + while (fgets(line2, 1024, in2) != NULL) { + lb2 = lblanks(line2); + if (lb2[0] == '[') { + strcat(text, lb2); + } else if (strstr(lb2, "freq")) { + strcat(text, lb2); + } + if ((int) strlen(text) > size/2) { + break; + } + } + fclose(in2); + } + if ((int) strlen(text) > size/2) { + break; + } + } + fclose(in1); + + if (0) fprintf(stderr, "%s", text); + + str = strdup(text); + p = strtok(str, "\n"); + maxn = -1; + extra = 0; + while (p) { + if (p[0] == '[') { + int ok = 1; + q = p+1; + while (*q) { + if (*q == ']') { + break; + } + if (! isdigit(*q)) { + if (0) fprintf(stderr, "extra: %s\n", p); + extra++; + ok = 0; + break; + } + q++; + } + if (ok) { + int n; + if (sscanf(p, "[%d]", &n) == 1) { + if (n > maxn) { + maxn = n; + } + if (0) fprintf(stderr, "maxn: %d %d\n", maxn, n); + } + } + + } + p = strtok(NULL, "\n"); + } + free(str); + + str = strdup(text); + p = strtok(str, "\n"); + extra = 0; + currn = 0; + if (v) fprintf(stderr, "\nname\tstation\tfreq (KHz)\n"); + while (p) { + if (p[0] == '[') { + int ok = 1; + strncpy(line, p, 100); + q = p+1; + while (*q) { + if (*q == ']') { + break; + } + if (! isdigit(*q)) { + extra++; + currn = maxn + extra; + ok = 0; + break; + } + q++; + } + if (ok) { + int n; + if (sscanf(p, "[%d]", &n) == 1) { + currn = n; + } + } + } + if (strstr(p, "freq") && (q = strchr(p, '=')) != NULL) { + int n; + q = lblanks(q+1); + if (sscanf(q, "%d", &n) == 1) { + if (currn >= 0 && currn < CHANNEL_MAX) { + if (v) fprintf(stderr, "%s\t%d\t%d\n", line, currn, n); + custom_freq[currn] = (unsigned long) n; + if (last_freq == 0) { + last_freq = custom_freq[currn]; + } + } + } + } + p = strtok(NULL, "\n"); + } + if (v) fprintf(stderr, "\n"); + v = 0; + free(str); + free(text); + free(dir); + free(file2); +} + +static void init_freqs(void) { + int i; + for (i=0; i