MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
v4lchannel.cpp
Go to the documentation of this file.
1 // Std C headers
2 #include <cstdio>
3 #include <cstdlib>
4 #include <cerrno>
5 
6 // POSIX headers
7 #include <unistd.h>
8 #include <fcntl.h>
9 #include <sys/ioctl.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/wait.h>
13 
14 // C++ headers
15 #include <algorithm>
16 #include <iostream>
17 using namespace std;
18 
19 #include <linux/videodev2.h>
20 
21 // MythTV headers
22 #include "v4lchannel.h"
23 #include "frequencies.h"
24 #include "tv_rec.h"
25 #include "mythdb.h"
26 #include "channelutil.h"
27 #include "cardutil.h"
28 
29 #define DEBUG_ATTRIB 1
30 
31 #define LOC QString("V4LChannel[%1](%2): ") \
32  .arg(GetCardID()).arg(GetDevice())
33 
34 static int format_to_mode(const QString &fmt);
35 static QString mode_to_format(int mode);
36 
37 V4LChannel::V4LChannel(TVRec *parent, const QString &videodevice)
38  : DTVChannel(parent),
39  device(videodevice), videofd(-1),
40  device_name(), driver_name(),
41  curList(NULL), totalChannels(0),
42  currentFormat(),
43  has_stream_io(false), has_std_io(false),
44  has_async_io(false),
45  has_tuner(false), has_sliced_vbi(false),
46 
47  defaultFreqTable(1)
48 {
49 }
50 
52 {
53  Close();
54 }
55 
56 bool V4LChannel::Init(QString &inputname, QString &startchannel, bool setchan)
57 {
58  if (setchan)
59  {
60  SetFormat(gCoreContext->GetSetting("TVFormat"));
62  }
63  return ChannelBase::Init(inputname, startchannel, setchan);
64 }
65 
66 bool V4LChannel::Open(void)
67 {
68 #if FAKE_VIDEO
69  return true;
70 #endif
71  if (videofd >= 0)
72  return true;
73 
74  QByteArray ascii_device = device.toLatin1();
75  videofd = open(ascii_device.constData(), O_RDWR);
76  if (videofd < 0)
77  {
78  LOG(VB_GENERAL, LOG_ERR, LOC + "Can't open video device." + ENO);
79  return false;
80  }
81 
82  uint32_t version, capabilities;
84  version, capabilities))
85  {
86  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to query capabilities." + ENO);
87  Close();
88  return false;
89  }
90 
91  has_stream_io = !!(capabilities & V4L2_CAP_STREAMING);
92  has_std_io = !!(capabilities & V4L2_CAP_READWRITE);
93  has_async_io = !!(capabilities & V4L2_CAP_ASYNCIO);
94  has_tuner = !!(capabilities & V4L2_CAP_TUNER);
95  has_sliced_vbi = !!(capabilities & V4L2_CAP_SLICED_VBI_CAPTURE);
96 
97  if (driver_name == "bttv" || driver_name == "cx8800")
98  has_stream_io = false; // driver workaround, see #9825 & #10519
99 
100  LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Device name '%1' driver '%2'.")
101  .arg(device_name).arg(driver_name));
102 
103  LOG(VB_CHANNEL, LOG_INFO, LOC +
104  QString("v4l2: stream io: %2 std io: %3 async io: %4 "
105  "tuner %5 sliced vbi %6")
106  .arg(has_stream_io).arg(has_std_io).arg(has_async_io)
107  .arg(has_tuner).arg(has_sliced_vbi));
108 
109  if (!InitializeInputs())
110  {
111  Close();
112  return false;
113  }
114 
115  SetFormat("Default");
116 
117  return true;
118 }
119 
121 {
122  if (videofd >= 0)
123  close(videofd);
124  videofd = -1;
125 }
126 
127 void V4LChannel::SetFd(int fd)
128 {
129  if (fd != videofd)
130  Close();
131  videofd = (fd >= 0) ? fd : -1;
132 }
133 
134 static int format_to_mode(const QString &fmt)
135 {
136  if (fmt == "PAL-BG")
137  return V4L2_STD_PAL_BG;
138  else if (fmt == "PAL-D")
139  return V4L2_STD_PAL_D;
140  else if (fmt == "PAL-DK")
141  return V4L2_STD_PAL_DK;
142  else if (fmt == "PAL-I")
143  return V4L2_STD_PAL_I;
144  else if (fmt == "PAL-60")
145  return V4L2_STD_PAL_60;
146  else if (fmt == "SECAM")
147  return V4L2_STD_SECAM;
148  else if (fmt == "SECAM-D")
149  return V4L2_STD_SECAM_D;
150  else if (fmt == "SECAM-DK")
151  return V4L2_STD_SECAM_DK;
152  else if (fmt == "PAL-NC")
153  return V4L2_STD_PAL_Nc;
154  else if (fmt == "PAL-M")
155  return V4L2_STD_PAL_M;
156  else if (fmt == "PAL-N")
157  return V4L2_STD_PAL_N;
158  else if (fmt == "NTSC-JP")
159  return V4L2_STD_NTSC_M_JP;
160  // generics...
161  else if (fmt.startsWith("NTSC"))
162  return V4L2_STD_NTSC;
163  else if (fmt.startsWith("ATSC"))
164  return V4L2_STD_NTSC; // We've dropped V4L ATSC support...
165  else if (fmt.startsWith("PAL"))
166  return V4L2_STD_PAL;
167  return V4L2_STD_NTSC;
168 }
169 
170 static QString mode_to_format(int mode)
171 {
172  if (mode == V4L2_STD_NTSC)
173  return "NTSC";
174  else if (mode == V4L2_STD_NTSC_M_JP)
175  return "NTSC-JP";
176  else if (mode == V4L2_STD_PAL)
177  return "PAL";
178  else if (mode == V4L2_STD_PAL_60)
179  return "PAL-60";
180  else if (mode == V4L2_STD_PAL_BG)
181  return "PAL-BG";
182  else if (mode == V4L2_STD_PAL_D)
183  return "PAL-D";
184  else if (mode == V4L2_STD_PAL_DK)
185  return "PAL-DK";
186  else if (mode == V4L2_STD_PAL_I)
187  return "PAL-I";
188  else if (mode == V4L2_STD_PAL_M)
189  return "PAL-M";
190  else if (mode == V4L2_STD_PAL_N)
191  return "PAL-N";
192  else if (mode == V4L2_STD_PAL_Nc)
193  return "PAL-NC";
194  else if (mode == V4L2_STD_SECAM)
195  return "SECAM";
196  else if (mode == V4L2_STD_SECAM_D)
197  return "SECAM-D";
198  // generic..
199  else if ((V4L2_STD_NTSC_M == mode) ||
200  (V4L2_STD_NTSC_443 == mode) ||
201  (V4L2_STD_NTSC_M_KR == mode))
202  return "NTSC";
203  else if ((V4L2_STD_PAL_B == mode) ||
204  (V4L2_STD_PAL_B1 == mode) ||
205  (V4L2_STD_PAL_G == mode) ||
206  (V4L2_STD_PAL_H == mode) ||
207  (V4L2_STD_PAL_D1 == mode) ||
208  (V4L2_STD_PAL_K == mode))
209  return "PAL";
210  else if ((V4L2_STD_SECAM_B == mode) ||
211  (V4L2_STD_SECAM_DK == mode) ||
212  (V4L2_STD_SECAM_G == mode) ||
213  (V4L2_STD_SECAM_H == mode) ||
214  (V4L2_STD_SECAM_K == mode) ||
215  (V4L2_STD_SECAM_K1 == mode) ||
216  (V4L2_STD_SECAM_L == mode) ||
217  (V4L2_STD_SECAM_LC == mode))
218  return "SECAM";
219  else if ((V4L2_STD_ATSC == mode) ||
220  (V4L2_STD_ATSC_8_VSB == mode) ||
221  (V4L2_STD_ATSC_16_VSB == mode))
222  {
223  // We've dropped V4L ATSC support, but this still needs to be
224  // returned here so we will change the mode if the device is
225  // in ATSC mode already.
226  return "ATSC";
227  }
228 
229  return "Unknown";
230 }
231 
239 {
240  // Get Inputs from DB
242  return false;
243 
244  // Get global TVFormat setting
245  QString fmt = gCoreContext->GetSetting("TVFormat");
246  LOG(VB_CHANNEL, LOG_INFO, QString("Global TVFormat Setting '%1'").arg(fmt));
247  int videomode_v4l2 = format_to_mode(fmt.toUpper());
248 
249  bool ok = false;
251 
252  // Insert info from hardware
253  uint valid_cnt = 0;
254  InputMap::const_iterator it;
255  for (it = m_inputs.begin(); it != m_inputs.end(); ++it)
256  {
257  InputNames::const_iterator v4l_it = v4l_inputs.begin();
258  for (; v4l_it != v4l_inputs.end(); ++v4l_it)
259  {
260  if (*v4l_it == (*it)->name)
261  {
262  (*it)->inputNumV4L = v4l_it.key();
263  (*it)->videoModeV4L2 = videomode_v4l2;
264  valid_cnt++;
265  }
266  }
267  }
268 
269  // print em
270  for (it = m_inputs.begin(); it != m_inputs.end(); ++it)
271  {
272  LOG(VB_CHANNEL, LOG_INFO, LOC +
273  QString("Input #%1: '%2' schan(%3) tun(%4) v4l2(%6)")
274  .arg(it.key()).arg((*it)->name).arg((*it)->startChanNum)
275  .arg((*it)->tuneToChannel)
276  .arg(mode_to_format((*it)->videoModeV4L2)));
277  }
278 
279  return valid_cnt;
280 }
281 
291 void V4LChannel::SetFormat(const QString &format)
292 {
293  if (!Open())
294  return;
295 
296  int inputNum = m_currentInputID;
297  if (m_currentInputID < 0)
298  inputNum = GetNextInputNum();
299 
300  QString fmt = format;
301  if ((fmt == "Default") || format.isEmpty())
302  {
303  InputMap::const_iterator it = m_inputs.find(inputNum);
304  if (it != m_inputs.end())
305  fmt = mode_to_format((*it)->videoModeV4L2);
306  }
307 
308  LOG(VB_CHANNEL, LOG_INFO, LOC + QString("SetFormat(%1) fmt(%2) input(%3)")
309  .arg(format).arg(fmt).arg(inputNum));
310 
311  if ((fmt == currentFormat) || SetInputAndFormat(inputNum, fmt))
312  {
313  currentFormat = fmt;
314  }
315 }
316 
318 {
320  return defaultFreqTable;
321 }
322 
324 {
327 }
328 
329 int V4LChannel::SetFreqTable(const QString &tablename)
330 {
331  QString name = tablename;
332  bool use_default = (name.toLower() == "default" || name.isEmpty());
333 
334  int i = 0;
335  char *listname = (char *)chanlists[i].name;
336 
337  curList = NULL;
338  while (listname != NULL)
339  {
340  if (use_default)
341  {
342  if (i == defaultFreqTable)
343  {
344  SetFreqTable(i);
345  return i;
346  }
347  }
348  else if (name == listname)
349  {
350  SetFreqTable(i);
351  return i;
352  }
353  i++;
354  listname = (char *)chanlists[i].name;
355  }
356 
357  LOG(VB_CHANNEL, LOG_ERR,
358  QString("Channel(%1)::SetFreqTable(): Invalid "
359  "frequency table name %2, using %3.").
360  arg(device).arg(name).arg((char *)chanlists[1].name));
361  SetFreqTable(1);
362  return 1;
363 }
364 
365 int V4LChannel::GetCurrentChannelNum(const QString &channame)
366 {
367  for (int i = 0; i < totalChannels; i++)
368  {
369  if (channame == curList[i].name)
370  return i;
371  }
372 
373  LOG(VB_GENERAL, LOG_ERR, LOC +
374  QString("GetCurrentChannelNum(%1): Failed to find Channel")
375  .arg(channame));
376 
377  return -1;
378 }
379 
380 bool V4LChannel::Tune(const QString &freqid, int finetune)
381 {
382  int i = GetCurrentChannelNum(freqid);
383  LOG(VB_CHANNEL, LOG_INFO,
384  QString("Channel(%1)::Tune(%2): curList[%3].freq(%4)")
385  .arg(device).arg(freqid).arg(i)
386  .arg((i != -1) ? curList[i].freq : -1));
387 
388  if (i == -1)
389  {
390  LOG(VB_GENERAL, LOG_ERR,
391  QString("Channel(%1)::Tune(%2): Error, failed to find channel.")
392  .arg(device).arg(freqid));
393  return false;
394  }
395 
396  int frequency = (curList[i].freq + finetune) * 1000;
397 
398  return Tune(frequency, "");
399 }
400 
401 bool V4LChannel::Tune(const DTVMultiplex &tuning, QString inputname)
402 {
403  return Tune(tuning.frequency - 1750000, // to visual carrier
404  inputname);
405 }
406 
418 bool V4LChannel::Tune(uint64_t frequency, QString inputname)
419 {
420  LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Tune(%1, %2)")
421  .arg(frequency).arg(inputname));
422 
423  int ioctlval = 0;
424 
425  int inputnum = GetInputByName(inputname);
426 
427  bool ok = true;
428  if ((inputnum >= 0) && (GetCurrentInputNum() != inputnum))
429  ok = SwitchToInput(inputnum, false);
430  else if (GetCurrentInputNum() < 0)
431  ok = SwitchToInput(0, false);
432 
433  if (!ok)
434  return false;
435 
436  // Video4Linux version 2 tuning
437  bool isTunerCapLow = false;
438  struct v4l2_modulator mod;
439  memset(&mod, 0, sizeof(mod));
440  mod.index = 0;
441  ioctlval = ioctl(videofd, VIDIOC_G_MODULATOR, &mod);
442  if (ioctlval >= 0)
443  {
444  isTunerCapLow = (mod.capability & V4L2_TUNER_CAP_LOW);
445  LOG(VB_CHANNEL, LOG_INFO,
446  QString(" name: %1").arg((char *)mod.name));
447  LOG(VB_CHANNEL, LOG_INFO, QString("CapLow: %1").arg(isTunerCapLow));
448  }
449 
450  struct v4l2_frequency vf;
451  memset(&vf, 0, sizeof(vf));
452 
453  vf.tuner = 0; // use first tuner
454  vf.frequency = (isTunerCapLow) ?
455  ((int)(frequency / 62.5)) : (frequency / 62500);
456 
457  vf.type = V4L2_TUNER_ANALOG_TV;
458 
459  ioctlval = ioctl(videofd, VIDIOC_S_FREQUENCY, &vf);
460  if (ioctlval < 0)
461  {
462  LOG(VB_GENERAL, LOG_ERR,
463  QString("Channel(%1)::Tune(): Error %2 "
464  "while setting frequency (v2): %3")
465  .arg(device).arg(ioctlval).arg(strerror(errno)));
466  return false;
467  }
468  ioctlval = ioctl(videofd, VIDIOC_G_FREQUENCY, &vf);
469 
470  if (ioctlval >= 0)
471  {
472  LOG(VB_CHANNEL, LOG_INFO,
473  QString("Channel(%1)::Tune(): Frequency is now %2")
474  .arg(device).arg(vf.frequency * 62500));
475  }
476 
477  return true;
478 }
479 
486 {
487  struct v4l2_frequency vf;
488  memset(&vf, 0, sizeof(vf));
489 
490  vf.tuner = 0; // use first tuner
491  vf.type = V4L2_TUNER_ANALOG_TV;
492 
493  // Get the last tuned frequency
494  int ioctlval = ioctl(videofd, VIDIOC_G_FREQUENCY, &vf);
495  if (ioctlval < 0)
496  {
497  LOG(VB_GENERAL, LOG_ERR, LOC + "Retune failed (1)" + ENO);
498  return false;
499  }
500 
501  // Set the last tuned frequency again...
502  ioctlval = ioctl(videofd, VIDIOC_S_FREQUENCY, &vf);
503  if (ioctlval < 0)
504  {
505  LOG(VB_GENERAL, LOG_ERR, LOC + "Retune failed (2)" + ENO);
506  return false;
507  }
508 
509  return true;
510 }
511 
512 QString V4LChannel::GetFormatForChannel(QString channum, QString inputname)
513 {
514  MSqlQuery query(MSqlQuery::InitCon());
515  query.prepare(
516  "SELECT tvformat "
517  "FROM channel, cardinput "
518  "WHERE channum = :CHANNUM AND "
519  " inputname = :INPUTNAME AND "
520  " cardinput.cardid = :CARDID AND "
521  " cardinput.sourceid = channel.sourceid");
522  query.bindValue(":CHANNUM", channum);
523  query.bindValue(":INPUTNAME", inputname);
524  query.bindValue(":CARDID", GetCardID());
525 
526  QString fmt = QString::null;
527  if (!query.exec() || !query.isActive())
528  MythDB::DBError("SwitchToInput:find format", query);
529  else if (query.next())
530  fmt = query.value(0).toString();
531  return fmt;
532 }
533 
534 bool V4LChannel::SetInputAndFormat(int inputNum, QString newFmt)
535 {
536  InputMap::const_iterator it = m_inputs.find(inputNum);
537  if (it == m_inputs.end() || (*it)->inputNumV4L < 0)
538  return false;
539 
540  int inputNumV4L = (*it)->inputNumV4L;
541  bool ok = true;
542 
543  QString msg =
544  QString("SetInputAndFormat(%1, %2) ").arg(inputNum).arg(newFmt);
545 
546  struct v4l2_input input;
547  int ioctlval = ioctl(videofd, VIDIOC_G_INPUT, &input);
548  bool input_switch = (0 != ioctlval || (uint)inputNumV4L != input.index);
549 
550  const v4l2_std_id new_vid_mode = format_to_mode(newFmt);
551  v4l2_std_id cur_vid_mode;
552  ioctlval = ioctl(videofd, VIDIOC_G_STD, &cur_vid_mode);
553  bool mode_switch = (0 != ioctlval || new_vid_mode != cur_vid_mode);
554  bool needs_switch = input_switch || mode_switch;
555 
556  LOG(VB_GENERAL, LOG_INFO, LOC + msg + "(v4l v2) " +
557  QString("input_switch: %1 mode_switch: %2")
558  .arg(input_switch).arg(mode_switch));
559 
560  // ConvertX (wis-go7007) requires streaming to be disabled
561  // before an input switch, do this if CAP_STREAMING is set.
562  bool streamingDisabled = false;
563  int streamType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
564  if (needs_switch && has_stream_io)
565  {
566  ioctlval = ioctl(videofd, VIDIOC_STREAMOFF, &streamType);
567  if (ioctlval < 0)
568  {
569  LOG(VB_GENERAL, LOG_ERR, LOC + msg +
570  "\n\t\t\twhile disabling streaming (v4l v2)" + ENO);
571  ok = false;
572  }
573  else
574  {
575  streamingDisabled = true;
576  }
577  }
578 
579  if (input_switch)
580  {
581  // Send the input switch ioctl.
582  ioctlval = ioctl(videofd, VIDIOC_S_INPUT, &inputNumV4L);
583  if (ioctlval < 0)
584  {
585  LOG(VB_GENERAL, LOG_ERR, LOC + msg +
586  "\n\t\t\twhile setting input (v4l v2)" + ENO);
587 
588  ok = false;
589  }
590  }
591 
592  if (mode_switch)
593  {
594  ioctlval = ioctl(videofd, VIDIOC_S_STD, &new_vid_mode);
595  if (ioctlval < 0)
596  {
597  LOG(VB_GENERAL, LOG_ERR, LOC + msg +
598  "\n\t\t\twhile setting format (v4l v2)" + ENO);
599 
600  ok = false;
601  }
602  }
603 
604  if (streamingDisabled)
605  {
606  ioctlval = ioctl(videofd, VIDIOC_STREAMON, &streamType);
607  if (ioctlval < 0)
608  {
609  LOG(VB_GENERAL, LOG_ERR, LOC + msg +
610  "\n\t\t\twhile reenabling streaming (v4l v2)" + ENO);
611 
612  ok = false;
613  }
614  }
615 
616  return ok;
617 }
618 
619 bool V4LChannel::SwitchToInput(int inputnum, bool setstarting)
620 {
621  InputMap::const_iterator it = m_inputs.find(inputnum);
622  if (it == m_inputs.end())
623  return false;
624 
625  QString tuneFreqId = (*it)->tuneToChannel;
626  QString channum = (*it)->startChanNum;
627  QString inputname = (*it)->name;
628 
629  LOG(VB_CHANNEL, LOG_INFO, QString("Channel(%1)::SwitchToInput(in %2, '%3')")
630  .arg(device).arg(inputnum)
631  .arg(setstarting ? channum : QString("")));
632 
633  uint mplexid_restriction;
634  if (!IsInputAvailable(inputnum, mplexid_restriction))
635  return false;
636 
637  QString newFmt = mode_to_format((*it)->videoModeV4L2);
638 
639  // If we are setting a channel, get its video mode...
640  bool chanValid = (channum != "Undefined") && !channum.isEmpty();
641  if (setstarting && chanValid)
642  {
643  QString tmp = GetFormatForChannel(channum, inputname);
644  if (tmp != "Default" && !tmp.isEmpty())
645  newFmt = tmp;
646  }
647 
648  bool ok = SetInputAndFormat(inputnum, newFmt);
649 
650  if (!ok)
651  {
652  LOG(VB_GENERAL, LOG_ERR, LOC + "SetInputAndFormat() failed");
653  return false;
654  }
655 
656  currentFormat = newFmt;
657  m_currentInputID = inputnum;
658  m_curchannelname = ""; // this will be set by SetChannelByString
659 
660  if (!tuneFreqId.isEmpty() && tuneFreqId != "Undefined")
661  ok = Tune(tuneFreqId, 0);
662 
663  if (!ok)
664  return false;
665 
666  if (setstarting && chanValid)
667  ok = SetChannelByString(channum);
668  else if (setstarting && !chanValid)
669  {
670  LOG(VB_GENERAL, LOG_ERR, LOC +
671  QString("SwitchToInput(in %2, set ch): ").arg(inputnum) +
672  QString("\n\t\t\tDefault channel '%1' is not valid.").arg(channum));
673  ok = false;
674  }
675 
676  return ok;
677 }
678 
679 static int get_v4l2_attribute(const QString &db_col_name)
680 {
681  if ("brightness" == db_col_name)
682  return V4L2_CID_BRIGHTNESS;
683  else if ("contrast" == db_col_name)
684  return V4L2_CID_CONTRAST;
685  else if ("colour" == db_col_name)
686  return V4L2_CID_SATURATION;
687  else if ("hue" == db_col_name)
688  return V4L2_CID_HUE;
689  return -1;
690 }
691 
692 bool V4LChannel::InitPictureAttribute(const QString db_col_name)
693 {
694  if (!m_pParent)
695  return false;
696 
697  int v4l2_attrib = get_v4l2_attribute(db_col_name);
698  if (v4l2_attrib == -1)
699  return false;
700 
701  int cfield = ChannelUtil::GetChannelValueInt(
702  db_col_name, GetCurrentSourceID(), m_curchannelname);
703  int sfield = CardUtil::GetValueInt(
704  db_col_name, GetCardID());
705 
706  if ((cfield == -1) || (sfield == -1))
707  return false;
708 
709  int field = (cfield + sfield) & 0xFFFF;
710 
711  QString loc = LOC +
712  QString("InitPictureAttribute(%1): ").arg(db_col_name, 10);
713 
714  struct v4l2_control ctrl;
715  struct v4l2_queryctrl qctrl;
716  memset(&ctrl, 0, sizeof(ctrl));
717  memset(&qctrl, 0, sizeof(qctrl));
718 
719  ctrl.id = qctrl.id = v4l2_attrib;
720  if (ioctl(videofd, VIDIOC_QUERYCTRL, &qctrl) < 0)
721  {
722  LOG(VB_GENERAL, LOG_ERR, loc + "failed to query controls." + ENO);
723  return false;
724  }
725 
726  float new_range = qctrl.maximum - qctrl.minimum;
727  float old_range = 65535 - 0;
728  float scl_range = new_range / old_range;
729  float dfl = (qctrl.default_value - qctrl.minimum) / new_range;
730  int norm_dfl = (0x10000 + (int)(dfl * old_range) - 32768) & 0xFFFF;
731 
732  if (pict_attr_default.find(db_col_name) == pict_attr_default.end())
733  {
734  if (device_name == "pcHDTV HD3000 HDTV")
735  {
736  pict_attr_default["brightness"] = 9830;
737  pict_attr_default["contrast"] = 39322;
738  pict_attr_default["colour"] = 45875;
739  pict_attr_default["hue"] = 0;
740  }
741  else
742  {
743  pict_attr_default[db_col_name] = norm_dfl;
744  }
745  }
746 
747  int dfield = pict_attr_default[db_col_name];
748  field = (cfield + sfield + dfield) & 0xFFFF;
749  int value0 = (int) ((scl_range * field) + qctrl.minimum);
750  int value1 = min(value0, (int)qctrl.maximum);
751  ctrl.value = max(value1, (int)qctrl.minimum);
752 
753 #if DEBUG_ATTRIB
754  LOG(VB_CHANNEL, LOG_DEBUG, loc + QString(" %1\n\t\t\t"
755  "[%2,%3] dflt(%4, %5, %6)")
756  .arg(value0).arg(qctrl.minimum, 5).arg(qctrl.maximum, 5)
757  .arg(qctrl.default_value, 5).arg(dfl, 4, 'f', 2)
758  .arg(norm_dfl));
759 #endif
760 
761  if (ioctl(videofd, VIDIOC_S_CTRL, &ctrl) < 0)
762  {
763  LOG(VB_GENERAL, LOG_ERR, loc + "failed to set controls" + ENO);
764  return false;
765  }
766 
767  return true;
768 }
769 
771 {
772  return (InitPictureAttribute("brightness") &&
773  InitPictureAttribute("contrast") &&
774  InitPictureAttribute("colour") &&
775  InitPictureAttribute("hue"));
776 }
777 
779 {
780  QString db_col_name = toDBString(attr);
781  if (db_col_name.isEmpty())
782  return -1;
783 
784  int cfield = ChannelUtil::GetChannelValueInt(
785  db_col_name, GetCurrentSourceID(), m_curchannelname);
786  int sfield = CardUtil::GetValueInt(
787  db_col_name, GetCardID());
788  int dfield = 0;
789 
790  if (pict_attr_default.find(db_col_name) != pict_attr_default.end())
791  dfield = pict_attr_default[db_col_name];
792 
793  int val = (cfield + sfield + dfield) & 0xFFFF;
794 
795 #if DEBUG_ATTRIB
796  LOG(VB_CHANNEL, LOG_DEBUG,
797  QString("GetPictureAttribute(%1) -> cdb %2 rdb %3 d %4 -> %5")
798  .arg(db_col_name).arg(cfield).arg(sfield)
799  .arg(dfield).arg(val));
800 #endif
801 
802  return val;
803 }
804 
805 static int get_v4l2_attribute_value(int videofd, int v4l2_attrib)
806 {
807  struct v4l2_control ctrl;
808  struct v4l2_queryctrl qctrl;
809  memset(&ctrl, 0, sizeof(ctrl));
810  memset(&qctrl, 0, sizeof(qctrl));
811 
812  ctrl.id = qctrl.id = v4l2_attrib;
813  if (ioctl(videofd, VIDIOC_QUERYCTRL, &qctrl) < 0)
814  {
815  LOG(VB_GENERAL, LOG_ERR,
816  "get_v4l2_attribute_value: failed to query controls (1)" + ENO);
817  return -1;
818  }
819 
820  if (ioctl(videofd, VIDIOC_G_CTRL, &ctrl) < 0)
821  {
822  LOG(VB_GENERAL, LOG_ERR,
823  "get_v4l2_attribute_value: failed to get controls (2)" + ENO);
824  return -1;
825  }
826 
827  float mult = 65535.0 / (qctrl.maximum - qctrl.minimum);
828  return min(max((int)(mult * (ctrl.value - qctrl.minimum)), 0), 65525);
829 }
830 
831 static int set_v4l2_attribute_value(int videofd, int v4l2_attrib, int newvalue)
832 {
833  struct v4l2_control ctrl;
834  struct v4l2_queryctrl qctrl;
835  memset(&ctrl, 0, sizeof(ctrl));
836  memset(&qctrl, 0, sizeof(qctrl));
837 
838  ctrl.id = qctrl.id = v4l2_attrib;
839  if (ioctl(videofd, VIDIOC_QUERYCTRL, &qctrl) < 0)
840  {
841  LOG(VB_GENERAL, LOG_ERR,
842  "set_v4l2_attribute_value: failed to query control" + ENO);
843  return -1;
844  }
845 
846  float mult = (qctrl.maximum - qctrl.minimum) / 65535.0;
847  ctrl.value = (int)(mult * newvalue + qctrl.minimum);
848  ctrl.value = min(ctrl.value, qctrl.maximum);
849  ctrl.value = max(ctrl.value, qctrl.minimum);
850 
851  if (ioctl(videofd, VIDIOC_S_CTRL, &ctrl) < 0)
852  {
853  LOG(VB_GENERAL, LOG_ERR,
854  "set_v4l2_attribute_value: failed to set control" + ENO);
855  return -1;
856  }
857 
858  return 0;
859 }
860 
862  PictureAdjustType type, PictureAttribute attr, bool up)
863 {
864  if (!m_pParent)
865  return -1;
866 
867  QString db_col_name = toDBString(attr);
868  if (db_col_name.isEmpty())
869  return -1;
870 
871  int v4l2_attrib = get_v4l2_attribute(db_col_name);
872  if (v4l2_attrib == -1)
873  return -1;
874 
875  // get the old attribute value from the hardware, this is
876  // just a sanity check on whether this attribute exists
877  if (get_v4l2_attribute_value(videofd, v4l2_attrib) < 0)
878  return -1;
879 
880  int old_value = GetPictureAttribute(attr);
881  int new_value = old_value + ((up) ? 655 : -655);
882 
883  // make sure we are within bounds (wrap around for hue)
884  if (V4L2_CID_HUE == v4l2_attrib)
885  new_value &= 0xffff;
886  new_value = min(max(new_value, 0), 65535);
887 
888 #if DEBUG_ATTRIB
889  LOG(VB_CHANNEL, LOG_DEBUG,
890  QString("ChangePictureAttribute(%1,%2,%3) cur %4 -> new %5")
891  .arg(type).arg(db_col_name).arg(up)
892  .arg(old_value).arg(new_value));
893 #endif
894 
895  // actually set the new attribute value on the hardware
896  if (set_v4l2_attribute_value(videofd, v4l2_attrib, new_value) < 0)
897  return -1;
898 
899  // tell the DB about the new attribute value
900  if (kAdjustingPicture_Channel == type)
901  {
902  int adj_value = ChannelUtil::GetChannelValueInt(
903  db_col_name, GetCurrentSourceID(), m_curchannelname);
904 
905  int tmp = new_value - old_value + adj_value;
906  tmp = (tmp < 0) ? tmp + 0x10000 : tmp;
907  tmp = (tmp > 0xffff) ? tmp - 0x10000 : tmp;
908  ChannelUtil::SetChannelValue(db_col_name, QString::number(tmp),
910  }
911  else if (kAdjustingPicture_Recording == type)
912  {
913  int adj_value = CardUtil::GetValueInt(
914  db_col_name, GetCardID());
915 
916  int tmp = new_value - old_value + adj_value;
917  tmp = (tmp < 0) ? tmp + 0x10000 : tmp;
918  tmp = (tmp > 0xffff) ? tmp - 0x10000 : tmp;
919  CardUtil::SetValue(db_col_name, GetCardID(),
920  GetCurrentSourceID(), tmp);
921  }
922 
923  return new_value;
924 }