00001
00002 #include <linux/videodev2.h>
00003 #include "linux/isp_user.h"
00004 #include <asm/types.h>
00005 #include <sys/types.h>
00006 #include <sys/syscall.h>
00007 #include <sys/prctl.h>
00008 #include <linux/capability.h>
00009
00010 #include <pthread.h>
00011 #include <poll.h>
00012
00013 #include <stdio.h>
00014 #include <stdlib.h>
00015 #include <string.h>
00016 #include <unistd.h>
00017 #include <math.h>
00018
00019 #include <sys/fcntl.h>
00020 #include <sys/ioctl.h>
00021 #include <sys/mman.h>
00022 #include <sys/time.h>
00023 #include <time.h>
00024
00025 #include <errno.h>
00026 #include <malloc.h>
00027
00028 #include "V4L2Sensor.h"
00029
00030 namespace FCam { namespace N900 {
00031
00032 V4L2Sensor *V4L2Sensor::instance(std::string fname) {
00033 std::map<std::string, V4L2Sensor *>::iterator i;
00034 i = instances_.find(fname);
00035 if (i == instances_.end()) {
00036 instances_[fname] = new V4L2Sensor(fname);
00037 }
00038
00039 return instances_[fname];
00040 };
00041
00042 V4L2Sensor::V4L2Sensor(std::string fname) : state(CLOSED), filename(fname) {
00043
00044 }
00045
00046 std::map<std::string, V4L2Sensor *> V4L2Sensor::instances_;
00047
00048 void V4L2Sensor::open() {
00049 if (state != CLOSED) {
00050 printf("Sensor is already open!\n");
00051 return;
00052 }
00053
00054 fd = ::open(filename.c_str(), O_RDWR | O_NONBLOCK, 0);
00055
00056 if (fd < 0) {
00057 perror("opening device");
00058 return;
00059 }
00060
00061 state = IDLE;
00062 }
00063
00064 void V4L2Sensor::close() {
00065 if (state != CLOSED) {
00066 ::close(fd);
00067 }
00068 state = CLOSED;
00069 }
00070
00071 int V4L2Sensor::getFD() {
00072 if (state == CLOSED) {
00073 return -1;
00074 }
00075 return fd;
00076 }
00077
00078 void V4L2Sensor::startStreaming(Mode m,
00079 HistogramConfig *histogram,
00080 SharpnessMapConfig *sharpness) {
00081
00082 if (state != IDLE) {
00083 printf("Can only initiate streaming if sensor is idle\n");
00084 return;
00085 }
00086
00087 struct v4l2_format fmt;
00088
00089 memset(&fmt, 0, sizeof(struct v4l2_format));
00090
00091 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
00092 fmt.fmt.pix.width = m.width;
00093 fmt.fmt.pix.height = m.height;
00094 if (m.type == UYVY) {
00095 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
00096 } else if (m.type == RAW) {
00097 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_SGRBG10;
00098 } else {
00099 perror("Unknown image format requested");
00100 return;
00101 }
00102 fmt.fmt.pix.field = V4L2_FIELD_NONE;
00103
00104
00105 if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
00106 perror("VIDIOC_S_FMT");
00107 return;
00108 }
00109
00110 currentMode.width = fmt.fmt.pix.width;
00111 currentMode.height = fmt.fmt.pix.height;
00112 currentMode.type = (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) ? UYVY : RAW;
00113
00114 struct v4l2_requestbuffers req;
00115 memset(&req, 0, sizeof(req));
00116 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
00117 req.memory = V4L2_MEMORY_MMAP;
00118 req.count = 8;
00119
00120 if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
00121 perror("VIDIOC_REQBUFS");
00122 return;
00123 }
00124
00125 buffers.resize(req.count);
00126
00127 for (size_t i = 0; i < buffers.size(); i++) {
00128 v4l2_buffer buf;
00129 memset(&buf, 0, sizeof(v4l2_buffer));
00130 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
00131 buf.memory = V4L2_MEMORY_MMAP;
00132 buf.index = i;
00133
00134 if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
00135 perror("VIDIOC_QUERYBUF");
00136 return;
00137 }
00138
00139 buffers[i].index = i;
00140 buffers[i].length = buf.length;
00141 buffers[i].data =
00142 (unsigned char *)mmap(NULL, buffers[i].length, PROT_READ | PROT_WRITE,
00143 MAP_SHARED, fd, buf.m.offset);
00144
00145 if (buffers[i].data == MAP_FAILED) {
00146 perror("mmap");
00147 return;
00148 }
00149 }
00150
00151 for (size_t i = 0; i < buffers.size(); i++) {
00152 printf("Queueing up buffer %d\n", i);
00153 releaseFrame(&buffers[i]);
00154 }
00155
00156
00157 setHistogramConfig(histogram);
00158 setSharpnessMapConfig(sharpness);
00159
00160 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
00161 if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
00162 perror("VIDIOC_STREAMON");
00163 return;
00164 }
00165
00166 printf("Sensor now streaming\n");
00167 state = STREAMING;
00168 }
00169
00170
00171 void V4L2Sensor::stopStreaming() {
00172 if (state != STREAMING) {
00173 printf("Camera is already not streaming!\n");
00174 return;
00175 }
00176
00177 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
00178 if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
00179 perror("VIDIOC_STREAMOFF");
00180 return;
00181 }
00182
00183 for (size_t i = 0; i < buffers.size(); i++) {
00184 if (munmap(buffers[i].data, buffers[i].length)) {
00185 perror("MUNMAP");
00186 }
00187 }
00188
00189 state = IDLE;
00190 }
00191
00192
00193
00194 V4L2Sensor::V4L2Frame *V4L2Sensor::acquireFrame(bool blocking) {
00195 if (state != STREAMING) {
00196 printf("Can't acquire a frame when not streaming!\n");
00197 return NULL;
00198 }
00199
00200
00201
00202 v4l2_buffer buf;
00203 memset(&buf, 0, sizeof(v4l2_buffer));
00204 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
00205 buf.memory = V4L2_MEMORY_MMAP;
00206
00207 if (blocking) {
00208 struct pollfd p = {fd, POLLIN, 0};
00209 poll(&p, 1, -1);
00210 if (!(p.revents & POLLIN)) {
00211 perror("Poll returned without data being available");
00212 return NULL;
00213 }
00214 }
00215
00216 if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
00217 if (errno == EAGAIN && !blocking) {
00218 printf("VIDIOC_DQBUF: EAGAIN\n"); fflush(stdout);
00219 return NULL;
00220 }
00221
00222 perror("VIDIOC_DQBUF");
00223 return NULL;
00224 }
00225
00226
00227
00228
00229 buffers[buf.index].processingDoneTime = Time(buf.timestamp);
00230 return &(buffers[buf.index]);
00231 }
00232
00233
00234
00235 void V4L2Sensor::getHistogram(Time t, Histogram *histogram, HistogramConfig *conf) {
00236
00237
00238 if (!histogram || !conf) return;
00239
00240 if (!conf->regions) {
00241 histogram->data = NULL;
00242 wipeHistogram(histogram);
00243 return;
00244 }
00245
00246
00247
00248 struct isp_hist_data hist_data;
00249 histogram->data = new unsigned[currentHistogram.regions * currentHistogram.buckets * 4];
00250 hist_data.hist_statistics_buf = histogram->data;
00251 hist_data.update = REQUEST_STATISTICS;
00252
00253
00254
00255 hist_data.frame_number = NEWEST_FRAME;
00256 hist_data.curr_frame = 0;
00257 hist_data.config_counter = 0;
00258 hist_data.ts.tv_sec = 0;
00259 hist_data.ts.tv_usec = 0;
00260
00261 if (ioctl(fd, VIDIOC_PRIVATE_ISP_HIST_REQ, &hist_data)) {
00262 perror("VIDIOC_PRIVATE_ISP_HIST_REQ");
00263 wipeHistogram(histogram); return;
00264 }
00265
00266
00267
00268
00269
00270 Time h(hist_data.ts);
00271
00272 if ((t - h) > 2000) {
00273 printf("WARNING: Missing histogram (%d)\n", t-h);
00274 wipeHistogram(histogram); return;
00275 }
00276
00277 while ((t-h) < -2000) {
00278
00279
00280 if (hist_data.frame_number == 0) hist_data.frame_number = 4095;
00281 else hist_data.frame_number--;
00282
00283
00284 if (ioctl(fd, VIDIOC_PRIVATE_ISP_HIST_REQ, &hist_data)) {
00285 perror("VIDIOC_PRIVATE_ISP_HIST_REQ");
00286 wipeHistogram(histogram); return;
00287 }
00288
00289 h = Time(hist_data.ts);
00290 }
00291
00292 histogram->valid = true;
00293 histogram->channels = 4;
00294 histogram->buckets = currentHistogram.buckets;
00295 histogram->regions = currentHistogram.regions;
00296 for (int i = 0; i < 4; i++) {
00297 histogram->region[i] = currentHistogram.region[i];
00298 }
00299 }
00300
00301 void V4L2Sensor::wipeHistogram(Histogram *histogram) {
00302 if (histogram->data) delete[] histogram->data;
00303 histogram->valid = false;
00304 histogram->data = NULL;
00305 histogram->regions = 0;
00306 histogram->channels = 0;
00307 histogram->buckets = 0;
00308 }
00309
00310 void V4L2Sensor::getSharpnessMap(Time t, SharpnessMap *sharpness, SharpnessMapConfig *conf) {
00311 if (!sharpness || !conf) return;
00312
00313 if (!conf->enabled) {
00314 sharpness->data = NULL;
00315 wipeSharpnessMap(sharpness);
00316 return;
00317 }
00318
00319
00320 struct isp_af_data af_data;
00321 af_data.frame_number = NEWEST_FRAME;
00322 af_data.update = REQUEST_STATISTICS;
00323 af_data.curr_frame = 0;
00324 af_data.config_counter = 0;
00325 af_data.xtrastats.ts.tv_sec = 0;
00326 af_data.xtrastats.ts.tv_usec = 0;
00327 sharpness->data = new unsigned[12*currentSharpness.size.width*currentSharpness.size.height];
00328 af_data.af_statistics_buf = sharpness->data;
00329
00330 if (ioctl(fd, VIDIOC_PRIVATE_ISP_AF_REQ, &af_data)) {
00331 perror("VIDIOC_PRIVATE_ISP_AF_REQ");
00332 wipeSharpnessMap(sharpness); return;
00333 }
00334
00335 Time s(af_data.xtrastats.ts);
00336 if ((t - s) > 2000) {
00337 printf("WARNING: Missing sharpness (%d)\n", t-s);
00338 wipeSharpnessMap(sharpness); return;
00339 }
00340
00341 while ((t-s) < -2000) {
00342
00343
00344 if (af_data.frame_number == 0) af_data.frame_number = 4095;
00345 else af_data.frame_number--;
00346
00347
00348 if (ioctl(fd, VIDIOC_PRIVATE_ISP_AF_REQ, &af_data)) {
00349 perror("VIDIOC_PRIVATE_ISP_AF_REQ");
00350 wipeSharpnessMap(sharpness); return;
00351 }
00352 s = Time(af_data.xtrastats.ts);
00353 }
00354
00355 sharpness->valid = true;
00356 sharpness->channels = 3;
00357 sharpness->size = currentSharpness.size;
00358 }
00359
00360 void V4L2Sensor::wipeSharpnessMap(SharpnessMap *sharpness) {
00361 if (sharpness->data) delete[] sharpness->data;
00362 sharpness->valid = false;
00363 sharpness->data = NULL;
00364 sharpness->size = FCam::Size(0, 0);
00365 }
00366
00367 void V4L2Sensor::setHistogramConfig(HistogramConfig *histogram) {
00368 if (!histogram) return;
00369 if (!histogram->regions) return;
00370
00371
00372 isp_pipeline_stats pstats;
00373 if (ioctl(fd, VIDIOC_PRIVATE_ISP_PIPELINE_STATS_REQ, &pstats) < 0) {
00374 perror("VIDIOC_PRIVATE_ISP_PIPELINE_STATS_REQ");
00375 return;
00376 }
00377
00378
00379 printf("CCDC output: %d x %d\n", pstats.ccdc_out_w, pstats.ccdc_out_h);
00380 printf("PRV output: %d x %d\n", pstats.prv_out_w, pstats.prv_out_h);
00381 printf("RSZ input: %d x %d + %d, %d\n",
00382 pstats.rsz_in_w, pstats.rsz_in_h,
00383 pstats.rsz_in_x, pstats.rsz_in_y);
00384 printf("RSZ output: %d x %d\n", pstats.rsz_out_w, pstats.rsz_out_h);
00385
00386 struct isp_hist_config hist_cfg;
00387 hist_cfg.enable = 1;
00388 hist_cfg.source = HIST_SOURCE_CCDC;
00389 hist_cfg.input_bit_width = 10;
00390 hist_cfg.num_acc_frames = 1;
00391 hist_cfg.hist_bins = HIST_BINS_64;
00392 hist_cfg.cfa = HIST_CFA_BAYER;
00393
00394
00395
00396
00397 hist_cfg.wg[0] = 35;
00398 hist_cfg.wg[1] = 35;
00399 hist_cfg.wg[2] = 35;
00400 hist_cfg.wg[3] = 35;
00401 hist_cfg.num_regions = histogram->regions;
00402
00403 for (int i = 0; i < histogram->regions; i++) {
00404
00405 unsigned x = ((unsigned)histogram->region[i].x * pstats.ccdc_out_w) / currentMode.width;
00406 unsigned y = ((unsigned)histogram->region[i].y * pstats.ccdc_out_h) / currentMode.height;
00407 unsigned w = ((unsigned)histogram->region[i].width * pstats.ccdc_out_w) / currentMode.width;
00408 unsigned h = ((unsigned)histogram->region[i].height * pstats.ccdc_out_h) / currentMode.height;
00409 if (x > pstats.ccdc_out_w) x = pstats.ccdc_out_w-1;
00410 if (y > pstats.ccdc_out_h) y = pstats.ccdc_out_h-1;
00411 if (w > pstats.ccdc_out_w) w = pstats.ccdc_out_w-x;
00412 if (h > pstats.ccdc_out_h) h = pstats.ccdc_out_h-y;
00413 hist_cfg.reg_hor[i] = (x << 16) | w;
00414 hist_cfg.reg_ver[i] = (y << 16) | h;
00415
00416 }
00417
00418 printf("Enabling histogram generator\n");
00419
00420 if (ioctl(fd, VIDIOC_PRIVATE_ISP_HIST_CFG, &hist_cfg)) {
00421 perror("VIDIOC_PRIVATE_ISP_HIST_CFG");
00422 return;
00423 }
00424
00425 currentHistogram = *histogram;
00426 currentHistogram.buckets = 64;
00427 }
00428
00429 void V4L2Sensor::setSharpnessMapConfig(SharpnessMapConfig *sharpness) {
00430 if (!sharpness) return;
00431 if (!sharpness->enabled) return;
00432
00433
00434 isp_pipeline_stats pstats;
00435 if (ioctl(fd, VIDIOC_PRIVATE_ISP_PIPELINE_STATS_REQ, &pstats) < 0) {
00436 perror("VIDIOC_PRIVATE_ISP_PIPELINE_STATS_REQ");
00437 return;
00438 }
00439
00440 printf("CCDC output: %d x %d\n", pstats.ccdc_out_w, pstats.ccdc_out_h);
00441 printf("PRV output: %d x %d\n", pstats.prv_out_w, pstats.prv_out_h);
00442 printf("RSZ input: %d x %d + %d, %d\n",
00443 pstats.rsz_in_w, pstats.rsz_in_h,
00444 pstats.rsz_in_x, pstats.rsz_in_y);
00445 printf("RSZ output: %d x %d\n", pstats.rsz_out_w, pstats.rsz_out_h);
00446
00447 struct af_configuration af_config;
00448
00449 af_config.alaw_enable = H3A_AF_ALAW_DISABLE;
00450 af_config.hmf_config.enable = H3A_AF_HMF_ENABLE;
00451 af_config.hmf_config.threshold = 10;
00452 af_config.rgb_pos = RG_GB_BAYER;
00453 af_config.iir_config.hz_start_pos = 0;
00454
00455
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465
00466
00467
00468 af_config.iir_config.coeff_set0[0] = 32;
00469
00470 af_config.iir_config.coeff_set0[1] = 4096-27;
00471 af_config.iir_config.coeff_set0[2] = 6;
00472
00473 af_config.iir_config.coeff_set0[3] = 16;
00474 af_config.iir_config.coeff_set0[4] = 4096-32;
00475 af_config.iir_config.coeff_set0[5] = 16;
00476
00477 af_config.iir_config.coeff_set0[6] = 0;
00478 af_config.iir_config.coeff_set0[7] = 0;
00479
00480 af_config.iir_config.coeff_set0[8] = 32;
00481 af_config.iir_config.coeff_set0[9] = 0;
00482 af_config.iir_config.coeff_set0[10] = 0;
00483
00484
00485
00486 af_config.iir_config.coeff_set1[0] = 32;
00487
00488 af_config.iir_config.coeff_set1[1] = 0;
00489 af_config.iir_config.coeff_set1[2] = 0;
00490
00491 af_config.iir_config.coeff_set1[3] = 16;
00492 af_config.iir_config.coeff_set1[4] = 4096-32;
00493 af_config.iir_config.coeff_set1[5] = 16;
00494
00495 af_config.iir_config.coeff_set1[6] = 0;
00496 af_config.iir_config.coeff_set1[7] = 0;
00497
00498 af_config.iir_config.coeff_set1[8] = 32;
00499 af_config.iir_config.coeff_set1[9] = 0;
00500 af_config.iir_config.coeff_set1[10] = 0;
00501
00502 af_config.mode = ACCUMULATOR_SUMMED;
00503 af_config.af_config = H3A_AF_CFG_ENABLE;
00504 int paxWidth = ((pstats.ccdc_out_w-4) / (2*sharpness->size.width))*2;
00505 int paxHeight = ((pstats.ccdc_out_h-4) / (2*sharpness->size.height))*2;
00506 if (paxWidth > 256) {
00507 printf("ERROR: AF paxels are too wide. Use a higher resolution sharpness map\n");
00508 return;
00509 }
00510 if (paxHeight > 256) {
00511 printf("ERROR: AF paxels are too tall. Use a higher resolution sharpness map\n");
00512 return;
00513 }
00514 if (paxWidth < 16) {
00515 printf("ERROR: AF paxels are too narrow. Use a lower resolution sharpness map\n");
00516 return;
00517 }
00518 if (paxHeight < 2) {
00519 printf("ERROR: AF paxels are too short. Use a lower resolution sharpness map\n");
00520 return;
00521 }
00522
00523
00524 af_config.paxel_config.width = (paxWidth-2)/2;
00525 af_config.paxel_config.height = (paxHeight-2)/2;
00526 af_config.paxel_config.hz_start = (pstats.ccdc_out_w - sharpness->size.width * paxWidth)/2;
00527 af_config.paxel_config.vt_start = (pstats.ccdc_out_h - sharpness->size.height * paxHeight)/2;
00528 af_config.paxel_config.hz_cnt = sharpness->size.width-1;
00529 af_config.paxel_config.vt_cnt = sharpness->size.height-1;
00530 af_config.paxel_config.line_incr = 0;
00531
00532 printf("Enabling sharpness detector\n");
00533 if (ioctl(fd, VIDIOC_PRIVATE_ISP_AF_CFG, &af_config)) {
00534 perror("VIDIOC_PRIVATE_ISP_AF_CFG");
00535 return;
00536 }
00537
00538 currentSharpness = *sharpness;
00539 }
00540
00541 void V4L2Sensor::releaseFrame(V4L2Frame *frame) {
00542
00543
00544
00545
00546
00547 v4l2_buffer buf;
00548 memset(&buf, 0, sizeof(v4l2_buffer));
00549 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
00550 buf.memory = V4L2_MEMORY_MMAP;
00551 buf.index = frame->index;
00552
00553 if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
00554 perror("VIDIOC_QBUF");
00555 return;
00556 }
00557 }
00558
00559 void V4L2Sensor::setControl(unsigned int id, int value) {
00560 if (state == CLOSED) return;
00561 v4l2_control ctrl;
00562 ctrl.id = id;
00563 ctrl.value = value;
00564 if (ioctl(fd, VIDIOC_S_CTRL, &ctrl) < 0) {
00565
00566 perror("VIDIOC_S_CTRL");
00567 return;
00568 }
00569 }
00570
00571 int V4L2Sensor::getControl(unsigned int id) {
00572 if (state == CLOSED) return -1;
00573 v4l2_control ctrl;
00574 ctrl.id = id;
00575 if (ioctl(fd, VIDIOC_G_CTRL, &ctrl) < 0) {
00576 perror("VIDIOC_G_CTRL");
00577 return -1;
00578 }
00579
00580 return ctrl.value;
00581 }
00582
00583 void V4L2Sensor::setExposure(int e) {
00584 if (state == CLOSED) return;
00585
00586 struct v4l2_control ctrl;
00587 ctrl.id = V4L2_CID_EXPOSURE;
00588 ctrl.value = e;
00589 if (ioctl(fd, VIDIOC_S_CTRL, &ctrl) < 0) {
00590 perror("VIDIOC_S_CTRL");
00591 return;
00592 }
00593
00594 }
00595
00596 int V4L2Sensor::getExposure() {
00597 if (state == CLOSED) return -1;
00598
00599 struct v4l2_control ctrl;
00600 ctrl.id = V4L2_CID_EXPOSURE;
00601
00602 if (ioctl(fd, VIDIOC_G_CTRL, &ctrl) < 0) {
00603 perror("VIDIOC_G_CTRL");
00604 return -1;
00605 }
00606
00607 return ctrl.value;
00608 }
00609
00610 #define V4L2_CID_FRAME_TIME (V4L2_CTRL_CLASS_CAMERA | 0x10ff)
00611
00612 void V4L2Sensor::setFrameTime(int e) {
00613 if (state == CLOSED) return;
00614
00615 struct v4l2_control ctrl;
00616 ctrl.id = V4L2_CID_FRAME_TIME;
00617 ctrl.value = e;
00618 if (ioctl(fd, VIDIOC_S_CTRL, &ctrl) < 0) {
00619 perror("VIDIOC_S_CTRL");
00620 return;
00621 }
00622 }
00623
00624 int V4L2Sensor::getFrameTime() {
00625 if (state == CLOSED) return -1;
00626
00627 struct v4l2_control ctrl;
00628 ctrl.id = V4L2_CID_FRAME_TIME;
00629
00630 if (ioctl(fd, VIDIOC_G_CTRL, &ctrl) < 0) {
00631 perror("VIDIOC_G_CTRL");
00632 return -1;
00633 }
00634
00635 return ctrl.value;
00636 }
00637
00638 void V4L2Sensor::setGain(float g) {
00639 if (state == CLOSED) return;
00640
00641 unsigned int gain;
00642 struct v4l2_control ctrl;
00643
00644 gain = (int)(g * 32.0 + 0.5);
00645
00646 ctrl.id = V4L2_CID_GAIN;
00647 ctrl.value = gain;
00648 if (ioctl(fd, VIDIOC_S_CTRL, &ctrl) < 0) {
00649 perror("VIDIOC_S_CTRL");
00650 return;
00651 }
00652 }
00653
00654 float V4L2Sensor::getGain() {
00655 if (state == CLOSED) return -1.0f;
00656
00657 struct v4l2_control ctrl;
00658
00659 ctrl.id = V4L2_CID_GAIN;
00660 if (ioctl(fd, VIDIOC_G_CTRL, &ctrl) < 0) {
00661 perror("VIDIOC_G_CTRL");
00662 return -1.0f;
00663 }
00664
00665 return ctrl.value / 32.0f;
00666 }
00667 }}
00668
00669