blob: 6b596d00b37e4de252fc8bbe3086cd84fadb0103 [file] [log] [blame]
#include <cctype>
#include "v4l2-ctl.h"
static v4l2_std_id standard; /* get_std/set_std */
static struct v4l2_dv_timings dv_timings; /* set_dv_bt_timings/get_dv_timings/query_dv_timings */
static bool query_and_set_dv_timings = false;
static bool cleared_dv_timings = false;
static int enum_and_set_dv_timings = -1;
static unsigned set_dv_timing_opts;
static __u32 list_dv_timings_pad;
static __u32 dv_timings_cap_pad;
void stds_usage()
{
printf("\nStandards/Timings options:\n"
" --list-standards display supported video standards [VIDIOC_ENUMSTD]\n"
" -S, --get-standard\n"
" query the video standard [VIDIOC_G_STD]\n"
" -s, --set-standard <num>\n"
" set the video standard to <num> [VIDIOC_S_STD]\n"
" <num> a numerical v4l2_std value, or one of:\n"
" pal or pal-X (X = B/G/H/N/Nc/I/D/K/M/60) (V4L2_STD_PAL)\n"
" ntsc or ntsc-X (X = M/J/K) (V4L2_STD_NTSC)\n"
" secam or secam-X (X = B/G/H/D/K/L/Lc) (V4L2_STD_SECAM)\n"
" --get-detected-standard\n"
" display detected input video standard [VIDIOC_QUERYSTD]\n"
" --list-dv-timings [<pad>]\n"
" list supp. standard dv timings [VIDIOC_ENUM_DV_TIMINGS]\n"
" for subdevs the pad can be specified (default is 0)\n"
" --set-dv-bt-timings\n"
" query: use the output of VIDIOC_QUERY_DV_TIMINGS\n"
" index=<index>: use the index as provided by --list-dv-timings\n"
" or specify timings using cvt/gtf options as follows:\n"
" cvt/gtf,width=<width>,height=<height>,fps=<frames per sec>\n"
" interlaced=<0/1>,reduced-blanking=<0/1/2>,reduced-fps=<0/1>\n"
" The value of reduced-blanking, if greater than 0, indicates\n"
" that reduced blanking is to be used and the value indicate the\n"
" version. For gtf, there is no version 2 for reduced blanking, and\n"
" the value 1 or 2 will give same results.\n"
" reduced-fps = 1, slows down pixel clock by factor of 1000 / 1001, allowing\n"
" to support NTSC frame rates like 29.97 or 59.94.\n"
" Reduced fps flag takes effect only with reduced blanking version 2 and,\n"
" when refresh rate is an integer multiple of 6, say, fps = 24,30,60 etc.\n"
" or update all or part of the current timings fields:\n"
" width=<width>,height=<height>,interlaced=<0/1>,\n"
" polarities=<polarities mask>,pixelclock=<pixelclock Hz>,\n"
" hfp=<horizontal front porch>,hs=<horizontal sync>,\n"
" hbp=<horizontal back porch>,vfp=<vertical front porch>,\n"
" vs=<vertical sync>,vbp=<vertical back porch>,\n"
" il_vfp=<vertical front porch for bottom field>,\n"
" il_vs=<vertical sync for bottom field>,\n"
" il_vbp=<vertical back porch for bottom field>.\n"
" clear: start with zeroed timings instead of the current timings.\n"
" set the digital video timings according to the BT 656/1120\n"
" standard [VIDIOC_S_DV_TIMINGS]\n"
" --get-dv-timings get the digital video timings in use [VIDIOC_G_DV_TIMINGS]\n"
" --query-dv-timings query the detected dv timings [VIDIOC_QUERY_DV_TIMINGS]\n"
" --get-dv-timings-cap [<pad>]\n"
" get the dv timings capabilities [VIDIOC_DV_TIMINGS_CAP]\n"
" for subdevs the pad can be specified (default is 0)\n"
);
}
static v4l2_std_id parse_pal(const char *pal)
{
if (pal[0] == '-') {
switch (tolower(pal[1])) {
case '6':
return V4L2_STD_PAL_60;
case 'b':
case 'g':
return V4L2_STD_PAL_BG;
case 'h':
return V4L2_STD_PAL_H;
case 'n':
if (tolower(pal[2]) == 'c')
return V4L2_STD_PAL_Nc;
return V4L2_STD_PAL_N;
case 'i':
return V4L2_STD_PAL_I;
case 'd':
case 'k':
return V4L2_STD_PAL_DK;
case 'm':
return V4L2_STD_PAL_M;
}
}
fprintf(stderr, "pal specifier not recognised\n");
stds_usage();
std::exit(EXIT_FAILURE);
}
static v4l2_std_id parse_secam(const char *secam)
{
if (secam[0] == '-') {
switch (tolower(secam[1])) {
case 'b':
case 'g':
case 'h':
return V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H;
case 'd':
case 'k':
return V4L2_STD_SECAM_DK;
case 'l':
if (tolower(secam[2]) == 'c')
return V4L2_STD_SECAM_LC;
return V4L2_STD_SECAM_L;
}
}
fprintf(stderr, "secam specifier not recognised\n");
stds_usage();
std::exit(EXIT_FAILURE);
}
static v4l2_std_id parse_ntsc(const char *ntsc)
{
if (ntsc[0] == '-') {
switch (tolower(ntsc[1])) {
case 'm':
return V4L2_STD_NTSC_M;
case 'j':
return V4L2_STD_NTSC_M_JP;
case 'k':
return V4L2_STD_NTSC_M_KR;
}
}
fprintf(stderr, "ntsc specifier not recognised\n");
stds_usage();
std::exit(EXIT_FAILURE);
}
enum timing_opts {
WIDTH = 0,
HEIGHT,
INTERLACED,
POLARITIES,
PIXEL_CLOCK,
HORZ_FRONT_PORCH,
HORZ_SYNC,
HORZ_BACK_PORCH,
VERT_FRONT_PORCH,
VERT_SYNC,
VERT_BACK_PORCH,
IL_VERT_FRONT_PORCH,
IL_VERT_SYNC,
IL_VERT_BACK_PORCH,
INDEX,
CVT,
GTF,
FPS,
REDUCED_BLANK,
REDUCED_FPS,
CLEAR,
MAX_TIMINGS_OPTIONS
};
static int parse_timing_subopt(char **subopt_str, int *value)
{
int opt;
char *opt_str;
static constexpr const char *subopt_list[] = {
"width",
"height",
"interlaced",
"polarities",
"pixelclock",
"hfp",
"hs",
"hbp",
"vfp",
"vs",
"vbp",
"il_vfp",
"il_vs",
"il_vbp",
"index",
"cvt",
"gtf",
"fps",
"reduced-blanking",
"reduced-fps",
"clear",
nullptr
};
opt = v4l_getsubopt(subopt_str, (char* const*) subopt_list, &opt_str);
if (opt == -1) {
fprintf(stderr, "Invalid suboptions specified\n");
stds_usage();
std::exit(EXIT_FAILURE);
}
if (opt_str == nullptr && opt != CVT && opt != GTF && opt != CLEAR) {
fprintf(stderr, "No value given to suboption <%s>\n",
subopt_list[opt]);
stds_usage();
std::exit(EXIT_FAILURE);
}
if (opt_str)
*value = strtol(opt_str, nullptr, 0);
return opt;
}
static void get_cvt_gtf_timings(char *subopt, int standard,
struct v4l2_bt_timings *bt)
{
int width = 0;
int height = 0;
int fps = 0;
int r_blank = 0;
bool interlaced = false;
bool reduced_fps = false;
bool timings_valid = false;
char *subopt_str = subopt;
while (*subopt_str != '\0') {
int opt;
int opt_val;
opt = parse_timing_subopt(&subopt_str, &opt_val);
switch (opt) {
case WIDTH:
width = opt_val;
break;
case HEIGHT:
height = opt_val;
break;
case FPS:
fps = opt_val;
break;
case REDUCED_BLANK:
r_blank = opt_val;
break;
case INTERLACED:
interlaced = opt_val;
break;
case REDUCED_FPS:
reduced_fps = opt_val;
break;
default:
break;
}
}
if (standard == V4L2_DV_BT_STD_CVT)
timings_valid = calc_cvt_modeline(width, height, fps, r_blank,
interlaced, reduced_fps, bt);
else
timings_valid = calc_gtf_modeline(width, height, fps, r_blank,
interlaced, bt);
if (!timings_valid) {
stds_usage();
std::exit(EXIT_FAILURE);
}
}
static void parse_dv_bt_timings(char *optarg, struct v4l2_dv_timings *dv_timings)
{
char *subs = optarg;
struct v4l2_bt_timings *bt = &dv_timings->bt;
bool parse_cvt_gtf = false;
dv_timings->type = V4L2_DV_BT_656_1120;
if (!strcmp(subs, "query")) {
query_and_set_dv_timings = true;
return;
}
while (*subs != '\0' && !parse_cvt_gtf) {
int opt;
int opt_val;
opt = parse_timing_subopt(&subs, &opt_val);
switch (opt) {
case WIDTH:
bt->width = opt_val;
break;
case HEIGHT:
bt->height = opt_val;
break;
case INTERLACED:
bt->interlaced = opt_val;
break;
case POLARITIES:
bt->polarities = opt_val;
break;
case PIXEL_CLOCK:
bt->pixelclock = opt_val;
break;
case HORZ_FRONT_PORCH:
bt->hfrontporch = opt_val;
break;
case HORZ_SYNC:
bt->hsync = opt_val;
break;
case HORZ_BACK_PORCH:
bt->hbackporch = opt_val;
break;
case VERT_FRONT_PORCH:
bt->vfrontporch = opt_val;
break;
case VERT_SYNC:
bt->vsync = opt_val;
break;
case VERT_BACK_PORCH:
bt->vbackporch = opt_val;
break;
case IL_VERT_FRONT_PORCH:
bt->il_vfrontporch = opt_val;
break;
case IL_VERT_SYNC:
bt->il_vsync = opt_val;
break;
case IL_VERT_BACK_PORCH:
bt->il_vbackporch = opt_val;
break;
case INDEX:
enum_and_set_dv_timings = opt_val;
break;
case CVT:
parse_cvt_gtf = true;
cleared_dv_timings = true;
get_cvt_gtf_timings(subs, V4L2_DV_BT_STD_CVT, bt);
break;
case GTF:
parse_cvt_gtf = true;
cleared_dv_timings = true;
get_cvt_gtf_timings(subs, V4L2_DV_BT_STD_GTF, bt);
break;
case REDUCED_FPS:
bt->flags |= opt_val ? V4L2_DV_FL_REDUCED_FPS : 0;
break;
case CLEAR:
cleared_dv_timings = true;
break;
default:
stds_usage();
std::exit(EXIT_FAILURE);
}
set_dv_timing_opts |= 1 << opt;
}
}
static void print_dv_timings(const struct v4l2_dv_timings *t)
{
const struct v4l2_bt_timings *bt;
double tot_width, tot_height;
switch (t->type) {
case V4L2_DV_BT_656_1120:
bt = &t->bt;
tot_height = bt->height +
bt->vfrontporch + bt->vsync + bt->vbackporch +
bt->il_vfrontporch + bt->il_vsync + bt->il_vbackporch;
tot_width = bt->width +
bt->hfrontporch + bt->hsync + bt->hbackporch;
if (options[OptConcise]) {
printf("\t%dx%d%c%.2f %s\n", bt->width, bt->height,
bt->interlaced ? 'i' : 'p',
static_cast<double>(bt->pixelclock) /
(tot_width * (tot_height / (bt->interlaced ? 2 : 1))),
dvflags2s(bt->vsync, bt->flags).c_str());
break;
}
printf("\tActive width: %d\n", bt->width);
printf("\tActive height: %d\n", bt->height);
printf("\tTotal width: %d\n",bt->width +
bt->hfrontporch + bt->hsync + bt->hbackporch);
printf("\tTotal height: %d\n", bt->height +
bt->vfrontporch + bt->vsync + bt->vbackporch +
bt->il_vfrontporch + bt->il_vsync + bt->il_vbackporch);
printf("\tFrame format: %s\n", bt->interlaced ? "interlaced" : "progressive");
printf("\tPolarities: %cvsync %chsync\n",
(bt->polarities & V4L2_DV_VSYNC_POS_POL) ? '+' : '-',
(bt->polarities & V4L2_DV_HSYNC_POS_POL) ? '+' : '-');
printf("\tPixelclock: %lld Hz", bt->pixelclock);
if (bt->width && bt->height) {
if (bt->interlaced)
printf(" (%.2f fields per second)", static_cast<double>(bt->pixelclock) /
(tot_width * (tot_height / 2)));
else
printf(" (%.2f frames per second)", static_cast<double>(bt->pixelclock) /
(tot_width * tot_height));
}
printf("\n");
printf("\tHorizontal frontporch: %d\n", bt->hfrontporch);
printf("\tHorizontal sync: %d\n", bt->hsync);
printf("\tHorizontal backporch: %d\n", bt->hbackporch);
if (bt->interlaced)
printf("\tField 1:\n");
printf("\tVertical frontporch: %d\n", bt->vfrontporch);
printf("\tVertical sync: %d\n", bt->vsync);
printf("\tVertical backporch: %d\n", bt->vbackporch);
if (bt->interlaced) {
printf("\tField 2:\n");
printf("\tVertical frontporch: %d\n", bt->il_vfrontporch);
printf("\tVertical sync: %d\n", bt->il_vsync);
printf("\tVertical backporch: %d\n", bt->il_vbackporch);
}
printf("\tStandards: %s\n", dv_standards2s(bt->standards).c_str());
if (bt->flags & V4L2_DV_FL_HAS_PICTURE_ASPECT)
printf("\tPicture aspect: %u:%u\n",
bt->picture_aspect.numerator,
bt->picture_aspect.denominator);
if (bt->flags & V4L2_DV_FL_HAS_CEA861_VIC)
printf("\tCTA-861 VIC: %u\n", bt->cea861_vic);
if (bt->flags & V4L2_DV_FL_HAS_HDMI_VIC)
printf("\tHDMI VIC: %u\n", bt->hdmi_vic);
printf("\tFlags: %s\n", dvflags2s(bt->vsync, bt->flags).c_str());
break;
default:
printf("Timing type not defined\n");
break;
}
}
void stds_cmd(int ch, char *optarg)
{
switch (ch) {
case OptSetStandard:
if (!strncasecmp(optarg, "pal", 3)) {
if (optarg[3])
standard = parse_pal(optarg + 3);
else
standard = V4L2_STD_PAL;
}
else if (!strncasecmp(optarg, "ntsc", 4)) {
if (optarg[4])
standard = parse_ntsc(optarg + 4);
else
standard = V4L2_STD_NTSC;
}
else if (!strncasecmp(optarg, "secam", 5)) {
if (optarg[5])
standard = parse_secam(optarg + 5);
else
standard = V4L2_STD_SECAM;
}
else if (isdigit(optarg[0])) {
standard = strtol(optarg, nullptr, 0) | (1ULL << 63);
} else {
fprintf(stderr, "Unknown standard '%s'\n", optarg);
stds_usage();
std::exit(EXIT_FAILURE);
}
break;
case OptSetDvBtTimings:
parse_dv_bt_timings(optarg, &dv_timings);
break;
case OptListDvTimings:
if (optarg)
list_dv_timings_pad = strtoul(optarg, nullptr, 0);
break;
case OptGetDvTimingsCap:
if (optarg)
dv_timings_cap_pad = strtoul(optarg, nullptr, 0);
break;
}
}
void stds_set(cv4l_fd &_fd)
{
int fd = _fd.g_fd();
if (options[OptSetStandard]) {
if (standard & (1ULL << 63)) {
struct v4l2_standard vs;
vs.index = standard & 0xffff;
if (test_ioctl(fd, VIDIOC_ENUMSTD, &vs) >= 0) {
standard = vs.id;
}
}
if (doioctl(fd, VIDIOC_S_STD, &standard) == 0)
printf("Standard set to %08llx\n", static_cast<unsigned long long>(standard));
}
if (options[OptSetDvBtTimings]) {
struct v4l2_enum_dv_timings et = {};
struct v4l2_dv_timings new_dv_timings = {};
if (query_and_set_dv_timings)
doioctl(fd, VIDIOC_QUERY_DV_TIMINGS, &new_dv_timings);
else if (enum_and_set_dv_timings >= 0) {
__u32 reduced_fps = dv_timings.bt.flags & V4L2_DV_FL_REDUCED_FPS;
et.index = enum_and_set_dv_timings;
doioctl(fd, VIDIOC_ENUM_DV_TIMINGS, &et);
et.timings.bt.flags |= reduced_fps;
new_dv_timings = et.timings;
} else if (cleared_dv_timings) {
new_dv_timings = dv_timings;
} else {
struct v4l2_bt_timings *bt = &new_dv_timings.bt;
struct v4l2_bt_timings *update_bt = &dv_timings.bt;
doioctl(fd, VIDIOC_G_DV_TIMINGS, &new_dv_timings);
for (unsigned i = 0; i < MAX_TIMINGS_OPTIONS; i++) {
if (!(set_dv_timing_opts & (1 << i)))
continue;
switch (i) {
case WIDTH:
bt->width = update_bt->width;
break;
case HEIGHT:
bt->height = update_bt->height;
break;
case INTERLACED:
bt->interlaced = update_bt->interlaced;
break;
case POLARITIES:
bt->polarities = update_bt->polarities;
break;
case PIXEL_CLOCK:
bt->pixelclock = update_bt->pixelclock;
break;
case HORZ_FRONT_PORCH:
bt->hfrontporch = update_bt->hfrontporch;
break;
case HORZ_SYNC:
bt->hsync = update_bt->hsync;
break;
case HORZ_BACK_PORCH:
bt->hbackporch = update_bt->hbackporch;
break;
case VERT_FRONT_PORCH:
bt->vfrontporch = update_bt->vfrontporch;
break;
case VERT_SYNC:
bt->vsync = update_bt->vsync;
break;
case VERT_BACK_PORCH:
bt->vbackporch = update_bt->vbackporch;
break;
case IL_VERT_FRONT_PORCH:
bt->il_vfrontporch = update_bt->il_vfrontporch;
break;
case IL_VERT_SYNC:
bt->il_vsync = update_bt->il_vsync;
break;
case IL_VERT_BACK_PORCH:
bt->il_vbackporch = update_bt->il_vbackporch;
break;
case REDUCED_FPS:
bt->flags &= ~V4L2_DV_FL_REDUCED_FPS;
bt->flags |= update_bt->flags & V4L2_DV_FL_REDUCED_FPS;
break;
}
}
}
if (verbose)
print_dv_timings(&new_dv_timings);
if (doioctl(fd, VIDIOC_S_DV_TIMINGS, &new_dv_timings) >= 0) {
printf("BT timings set\n");
}
}
}
void stds_get(cv4l_fd &_fd)
{
int fd = _fd.g_fd();
if (options[OptGetStandard]) {
if (doioctl(fd, VIDIOC_G_STD, &standard) == 0) {
printf("Video Standard = 0x%08llx\n", static_cast<unsigned long long>(standard));
printf("\t%s\n", std2s(standard, "\n\t").c_str());
}
}
if (options[OptGetDvTimings]) {
if (doioctl(fd, VIDIOC_G_DV_TIMINGS, &dv_timings) >= 0) {
printf("DV timings:\n");
print_dv_timings(&dv_timings);
}
}
if (options[OptGetDvTimingsCap]) {
struct v4l2_dv_timings_cap dv_timings_cap = {};
dv_timings_cap.pad = dv_timings_cap_pad;
if (doioctl(fd, VIDIOC_DV_TIMINGS_CAP, &dv_timings_cap) >= 0) {
struct v4l2_bt_timings_cap *bt = &dv_timings_cap.bt;
printf("DV timings capabilities:\n");
if (dv_timings_cap.type != V4L2_DV_BT_656_1120)
printf("\tUnknown type\n");
else {
printf("\tMinimum Width: %u\n", bt->min_width);
printf("\tMaximum Width: %u\n", bt->max_width);
printf("\tMinimum Height: %u\n", bt->min_height);
printf("\tMaximum Height: %u\n", bt->max_height);
printf("\tMinimum PClock: %llu\n", bt->min_pixelclock);
printf("\tMaximum PClock: %llu\n", bt->max_pixelclock);
printf("\tStandards: %s\n",
dv_standards2s(bt->standards).c_str());
printf("\tCapabilities: %s\n",
dv_caps2s(bt->capabilities).c_str());
}
}
}
if (options[OptQueryStandard]) {
if (doioctl(fd, VIDIOC_QUERYSTD, &standard) == 0) {
printf("Video Standard = 0x%08llx\n", static_cast<unsigned long long>(standard));
printf("\t%s\n", std2s(standard, "\n\t").c_str());
}
}
if (options[OptQueryDvTimings]) {
doioctl(fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings);
print_dv_timings(&dv_timings);
}
}
void stds_list(cv4l_fd &_fd)
{
int fd = _fd.g_fd();
if (options[OptListStandards]) {
struct v4l2_standard vs;
printf("ioctl: VIDIOC_ENUMSTD\n");
vs.index = 0;
while (test_ioctl(fd, VIDIOC_ENUMSTD, &vs) >= 0) {
if (options[OptConcise]) {
printf("\t%2d: 0x%016llX %s\n", vs.index,
static_cast<unsigned long long>(vs.id), vs.name);
} else {
if (vs.index)
printf("\n");
printf("\tIndex : %d\n", vs.index);
printf("\tID : 0x%016llX\n", static_cast<unsigned long long>(vs.id));
printf("\tName : %s\n", vs.name);
printf("\tFrame period: %d/%d\n",
vs.frameperiod.numerator,
vs.frameperiod.denominator);
printf("\tFrame lines : %d\n", vs.framelines);
}
vs.index++;
}
}
if (options[OptListDvTimings]) {
struct v4l2_enum_dv_timings dv_enum_timings = {};
printf("ioctl: VIDIOC_ENUM_DV_TIMINGS\n");
dv_enum_timings.pad = list_dv_timings_pad;
while (test_ioctl(fd, VIDIOC_ENUM_DV_TIMINGS, &dv_enum_timings) >= 0) {
if (options[OptConcise]) {
printf("\t%d:", dv_enum_timings.index);
} else {
if (dv_enum_timings.index)
printf("\n");
printf("\tIndex: %d\n", dv_enum_timings.index);
}
print_dv_timings(&dv_enum_timings.timings);
dv_enum_timings.index++;
}
}
}