|
MythTV
0.26-pre
|
00001 /* 00002 * Class DVBChannel 00003 * 00004 * Copyright (C) Kenneth Aafloy 2003 00005 * 00006 * Description: 00007 * Has the responsibility of opening the Frontend device and 00008 * setting the options to tune a channel. It also keeps other 00009 * channel options used by the dvb hierarcy. 00010 * 00011 * Author(s): 00012 * Taylor Jacob (rtjacob at earthlink.net) 00013 * - Changed tuning and DB structure 00014 * Kenneth Aafloy (ke-aa at frisurf.no) 00015 * - Rewritten for faster tuning. 00016 * Ben Bucksch 00017 * - Wrote the original implementation 00018 * 00019 * This program is free software; you can redistribute it and/or modify 00020 * it under the terms of the GNU General Public License as published by 00021 * the Free Software Foundation; either version 2 of the License, or 00022 * (at your option) any later version. 00023 * 00024 * This program is distributed in the hope that it will be useful, 00025 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00026 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00027 * GNU General Public License for more details. 00028 * 00029 * You should have received a copy of the GNU General Public License 00030 * along with this program; if not, write to the Free Software 00031 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 00032 */ 00033 00034 // POSIX headers 00035 #include <fcntl.h> 00036 #include <unistd.h> 00037 #include <sys/poll.h> 00038 #include <sys/select.h> 00039 #include <sys/time.h> 00040 #include <sys/types.h> 00041 00042 // MythTV headers 00043 #include "mythconfig.h" 00044 #include "mythdb.h" 00045 #include "cardutil.h" 00046 #include "channelutil.h" 00047 #include "dvbtypes.h" 00048 #include "dvbchannel.h" 00049 #include "dvbcam.h" 00050 00051 static void drain_dvb_events(int fd); 00052 static bool wait_for_backend(int fd, int timeout_ms); 00053 static struct dvb_frontend_parameters dtvmultiplex_to_dvbparams( 00054 DTVTunerType, const DTVMultiplex&, int intermediate_freq, bool can_fec_auto); 00055 static DTVMultiplex dvbparams_to_dtvmultiplex( 00056 DTVTunerType, const dvb_frontend_parameters&); 00057 00058 #define LOC QString("DVBChan(%1:%2): ").arg(GetCardID()).arg(device) 00059 00065 DVBChannel::DVBChannel(const QString &aDevice, TVRec *parent) 00066 : DTVChannel(parent), 00067 // Helper classes 00068 diseqc_tree(NULL), dvbcam(NULL), 00069 // Device info 00070 frontend_name(QString::null), 00071 // Tuning 00072 tune_lock(), hw_lock(QMutex::Recursive), 00073 last_lnb_dev_id(-1), 00074 tuning_delay(0), sigmon_delay(25), 00075 first_tune(true), 00076 // Misc 00077 fd_frontend(-1), device(aDevice), 00078 has_crc_bug(false) 00079 { 00080 master_map_lock.lockForWrite(); 00081 QString devname = CardUtil::GetDeviceName(DVB_DEV_FRONTEND, device); 00082 master_map[devname].push_back(this); // == RegisterForMaster 00083 DVBChannel *master = dynamic_cast<DVBChannel*>(master_map[devname].front()); 00084 if (master == this) 00085 { 00086 dvbcam = new DVBCam(device); 00087 has_crc_bug = CardUtil::HasDVBCRCBug(device); 00088 } 00089 else 00090 { 00091 dvbcam = master->dvbcam; 00092 has_crc_bug = master->has_crc_bug; 00093 } 00094 master_map_lock.unlock(); 00095 00096 sigmon_delay = CardUtil::GetMinSignalMonitoringDelay(device); 00097 } 00098 00099 DVBChannel::~DVBChannel() 00100 { 00101 // set a new master if there are other instances and we're the master 00102 // whether we are the master or not remove us from the map.. 00103 master_map_lock.lockForWrite(); 00104 QString devname = CardUtil::GetDeviceName(DVB_DEV_FRONTEND, device); 00105 DVBChannel *master = dynamic_cast<DVBChannel*>(master_map[devname].front()); 00106 if (master == this) 00107 { 00108 master_map[devname].pop_front(); 00109 DVBChannel *new_master = NULL; 00110 if (!master_map[devname].empty()) 00111 new_master = dynamic_cast<DVBChannel*>(master_map[devname].front()); 00112 if (new_master) 00113 new_master->is_open = master->is_open; 00114 } 00115 else 00116 { 00117 master_map[devname].removeAll(this); 00118 } 00119 master_map_lock.unlock(); 00120 00121 Close(); 00122 00123 // if we're the last one out delete dvbcam 00124 master_map_lock.lockForRead(); 00125 MasterMap::iterator mit = master_map.find(devname); 00126 if ((*mit).empty()) 00127 delete dvbcam; 00128 dvbcam = NULL; 00129 master_map_lock.unlock(); 00130 00131 // diseqc_tree is managed elsewhere 00132 } 00133 00134 void DVBChannel::Close(DVBChannel *who) 00135 { 00136 LOG(VB_CHANNEL, LOG_INFO, LOC + "Closing DVB channel"); 00137 00138 IsOpenMap::iterator it = is_open.find(who); 00139 if (it == is_open.end()) 00140 return; // this caller didn't have it open in the first place.. 00141 00142 is_open.erase(it); 00143 00144 QMutexLocker locker(&hw_lock); 00145 00146 DVBChannel *master = GetMasterLock(); 00147 if (master != NULL && master != this) 00148 { 00149 if (dvbcam->IsRunning()) 00150 dvbcam->SetPMT(this, NULL); 00151 master->Close(this); 00152 fd_frontend = -1; 00153 ReturnMasterLock(master); 00154 return; 00155 } 00156 ReturnMasterLock(master); // if we're the master we don't need this lock.. 00157 00158 if (!is_open.empty()) 00159 return; // not all callers have closed the DVB channel yet.. 00160 00161 if (diseqc_tree) 00162 diseqc_tree->Close(); 00163 00164 if (fd_frontend >= 0) 00165 { 00166 close(fd_frontend); 00167 fd_frontend = -1; 00168 00169 dvbcam->Stop(); 00170 } 00171 } 00172 00173 bool DVBChannel::Open(DVBChannel *who) 00174 { 00175 LOG(VB_CHANNEL, LOG_INFO, LOC + "Opening DVB channel"); 00176 00177 QMutexLocker locker(&hw_lock); 00178 00179 if (fd_frontend >= 0) 00180 { 00181 is_open[who] = true; 00182 return true; 00183 } 00184 00185 DVBChannel *master = GetMasterLock(); 00186 if (master != this) 00187 { 00188 if (!master->Open(who)) 00189 { 00190 ReturnMasterLock(master); 00191 return false; 00192 } 00193 00194 fd_frontend = master->fd_frontend; 00195 frontend_name = master->frontend_name; 00196 tunerType = master->tunerType; 00197 capabilities = master->capabilities; 00198 ext_modulations = master->ext_modulations; 00199 frequency_minimum = master->frequency_minimum; 00200 frequency_maximum = master->frequency_maximum; 00201 symbol_rate_minimum = master->symbol_rate_minimum; 00202 symbol_rate_maximum = master->symbol_rate_maximum; 00203 00204 is_open[who] = true; 00205 00206 if (!InitializeInputs()) 00207 { 00208 Close(); 00209 ReturnMasterLock(master); 00210 return false; 00211 } 00212 00213 ReturnMasterLock(master); 00214 return true; 00215 } 00216 ReturnMasterLock(master); // if we're the master we don't need this lock.. 00217 00218 QString devname = CardUtil::GetDeviceName(DVB_DEV_FRONTEND, device); 00219 QByteArray devn = devname.toAscii(); 00220 00221 for (int tries = 1; ; ++tries) 00222 { 00223 fd_frontend = open(devn.constData(), O_RDWR | O_NONBLOCK); 00224 if (fd_frontend >= 0) 00225 break; 00226 LOG(VB_GENERAL, LOG_WARNING, LOC + 00227 "Opening DVB frontend device failed." + ENO); 00228 if (tries >= 20 || (errno != EBUSY && errno != EAGAIN)) 00229 { 00230 LOG(VB_GENERAL, LOG_ERR, LOC + 00231 QString("Failed to open DVB frontend device due to " 00232 "fatal error or too many attempts.")); 00233 return false; 00234 } 00235 usleep(50000); 00236 } 00237 00238 dvb_frontend_info info; 00239 memset(&info, 0, sizeof(info)); 00240 if (ioctl(fd_frontend, FE_GET_INFO, &info) < 0) 00241 { 00242 LOG(VB_GENERAL, LOG_ERR, LOC + 00243 "Failed to get frontend information." + ENO); 00244 00245 close(fd_frontend); 00246 fd_frontend = -1; 00247 return false; 00248 } 00249 00250 frontend_name = info.name; 00251 tunerType = info.type; 00252 #if HAVE_FE_CAN_2G_MODULATION 00253 if (tunerType == DTVTunerType::kTunerTypeDVBS1 && 00254 (info.caps & FE_CAN_2G_MODULATION)) 00255 tunerType = DTVTunerType::kTunerTypeDVBS2; 00256 #endif // HAVE_FE_CAN_2G_MODULATION 00257 capabilities = info.caps; 00258 frequency_minimum = info.frequency_min; 00259 frequency_maximum = info.frequency_max; 00260 symbol_rate_minimum = info.symbol_rate_min; 00261 symbol_rate_maximum = info.symbol_rate_max; 00262 00263 LOG(VB_RECORD, LOG_INFO, LOC + 00264 QString("Using DVB card %1, with frontend '%2'.") 00265 .arg(device).arg(frontend_name)); 00266 00267 // Turn on the power to the LNB 00268 if (tunerType.IsDiSEqCSupported()) 00269 { 00270 diseqc_tree = diseqc_dev.FindTree(GetCardID()); 00271 if (diseqc_tree) 00272 diseqc_tree->Open(fd_frontend); 00273 } 00274 00275 dvbcam->Start(); 00276 00277 first_tune = true; 00278 00279 if (!InitializeInputs()) 00280 { 00281 Close(); 00282 return false; 00283 } 00284 00285 if (fd_frontend >= 0) 00286 is_open[who] = true; 00287 00288 return (fd_frontend >= 0); 00289 } 00290 00291 bool DVBChannel::IsOpen(void) const 00292 { 00293 IsOpenMap::const_iterator it = is_open.find(this); 00294 return it != is_open.end(); 00295 } 00296 00297 bool DVBChannel::Init(QString &inputname, QString &startchannel, bool setchan) 00298 { 00299 if (setchan && !IsOpen()) 00300 Open(this); 00301 00302 return ChannelBase::Init(inputname, startchannel, setchan); 00303 } 00304 00305 bool DVBChannel::SwitchToInput(const QString &inputname, const QString &chan) 00306 { 00307 int input = GetInputByName(inputname); 00308 00309 bool ok = false; 00310 if (input >= 0) 00311 { 00312 m_currentInputID = input; 00313 ok = SetChannelByString(chan); 00314 } 00315 else 00316 { 00317 LOG(VB_GENERAL, LOG_ERR, LOC + 00318 QString("DVBChannel: Could not find input: %1 on card when " 00319 "setting channel %2\n").arg(inputname).arg(chan)); 00320 } 00321 return ok; 00322 } 00323 00324 bool DVBChannel::SwitchToInput(int newInputNum, bool setstarting) 00325 { 00326 if (!ChannelBase::SwitchToInput(newInputNum, false)) 00327 return false; 00328 00329 m_currentInputID = newInputNum; 00330 InputMap::const_iterator it = m_inputs.find(m_currentInputID); 00331 return SetChannelByString((*it)->startChanNum); 00332 } 00333 00334 00338 void DVBChannel::CheckFrequency(uint64_t frequency) const 00339 { 00340 if (frequency_minimum && frequency_maximum && 00341 (frequency_minimum <= frequency_maximum) && 00342 (frequency < frequency_minimum || frequency > frequency_maximum)) 00343 { 00344 LOG(VB_GENERAL, LOG_WARNING, LOC + 00345 QString("Your frequency setting (%1) is out of range. " 00346 "(min/max:%2/%3)") 00347 .arg(frequency).arg(frequency_minimum).arg(frequency_maximum)); 00348 } 00349 } 00350 00351 void DVBChannel::CheckOptions(DTVMultiplex &tuning) const 00352 { 00353 if ((tuning.inversion == DTVInversion::kInversionAuto) && 00354 !(capabilities & FE_CAN_INVERSION_AUTO)) 00355 { 00356 LOG(VB_GENERAL, LOG_WARNING, LOC + 00357 "'Auto' inversion parameter unsupported by this driver, " 00358 "falling back to 'off'."); 00359 tuning.inversion = DTVInversion::kInversionOff; 00360 } 00361 00362 // DVB-S needs a fully initialized diseqc tree and is checked later in Tune 00363 if (!diseqc_tree) 00364 CheckFrequency(tuning.frequency); 00365 00366 if (tunerType.IsFECVariable() && 00367 symbol_rate_minimum && symbol_rate_maximum && 00368 (symbol_rate_minimum <= symbol_rate_maximum) && 00369 (tuning.symbolrate < symbol_rate_minimum || 00370 tuning.symbolrate > symbol_rate_maximum)) 00371 { 00372 LOG(VB_GENERAL, LOG_WARNING, LOC + 00373 QString("Symbol Rate setting (%1) is out of range (min/max:%2/%3)") 00374 .arg(tuning.symbolrate) 00375 .arg(symbol_rate_minimum).arg(symbol_rate_maximum)); 00376 } 00377 00378 if (tunerType.IsFECVariable() && !CheckCodeRate(tuning.fec)) 00379 { 00380 LOG(VB_GENERAL, LOG_WARNING, LOC + 00381 "Selected fec_inner parameter unsupported by this driver."); 00382 } 00383 00384 if (tunerType.IsModulationVariable() && !CheckModulation(tuning.modulation)) 00385 { 00386 LOG(VB_GENERAL, LOG_WARNING, LOC + 00387 "Selected modulation parameter unsupported by this driver."); 00388 } 00389 00390 if (DTVTunerType::kTunerTypeDVBT != tunerType) 00391 { 00392 LOG(VB_CHANNEL, LOG_INFO, LOC + tuning.toString()); 00393 return; 00394 } 00395 00396 // Check OFDM Tuning params 00397 00398 if (!CheckCodeRate(tuning.hp_code_rate)) 00399 { 00400 LOG(VB_GENERAL, LOG_WARNING, LOC + 00401 "Selected code_rate_hp parameter unsupported by this driver."); 00402 } 00403 00404 if (!CheckCodeRate(tuning.lp_code_rate)) 00405 { 00406 LOG(VB_GENERAL, LOG_WARNING, LOC + 00407 "Selected code_rate_lp parameter unsupported by this driver."); 00408 } 00409 00410 if ((tuning.bandwidth == DTVBandwidth::kBandwidthAuto) && 00411 !(capabilities & FE_CAN_BANDWIDTH_AUTO)) 00412 { 00413 LOG(VB_GENERAL, LOG_WARNING, LOC + 00414 "'Auto' bandwidth parameter unsupported by this driver."); 00415 } 00416 00417 if ((tuning.trans_mode == DTVTransmitMode::kTransmissionModeAuto) && 00418 !(capabilities & FE_CAN_TRANSMISSION_MODE_AUTO)) 00419 { 00420 LOG(VB_GENERAL, LOG_WARNING, LOC + 00421 "'Auto' transmission_mode parameter unsupported by this driver."); 00422 } 00423 00424 if ((tuning.guard_interval == DTVGuardInterval::kGuardIntervalAuto) && 00425 !(capabilities & FE_CAN_GUARD_INTERVAL_AUTO)) 00426 { 00427 LOG(VB_GENERAL, LOG_WARNING, LOC + 00428 "'Auto' guard_interval parameter unsupported by this driver."); 00429 } 00430 00431 if ((tuning.hierarchy == DTVHierarchy::kHierarchyAuto) && 00432 !(capabilities & FE_CAN_HIERARCHY_AUTO)) 00433 { 00434 LOG(VB_GENERAL, LOG_WARNING, LOC + 00435 "'Auto' hierarchy parameter unsupported by this driver. "); 00436 } 00437 00438 if (!CheckModulation(tuning.modulation)) 00439 { 00440 LOG(VB_GENERAL, LOG_WARNING, LOC + 00441 "Selected modulation parameter unsupported by this driver."); 00442 } 00443 00444 LOG(VB_CHANNEL, LOG_INFO, LOC + tuning.toString()); 00445 } 00446 00450 bool DVBChannel::CheckCodeRate(DTVCodeRate rate) const 00451 { 00452 const uint64_t caps = capabilities; 00453 return 00454 ((DTVCodeRate::kFECNone == rate)) || 00455 ((DTVCodeRate::kFEC_1_2 == rate) && (caps & FE_CAN_FEC_1_2)) || 00456 ((DTVCodeRate::kFEC_2_3 == rate) && (caps & FE_CAN_FEC_2_3)) || 00457 ((DTVCodeRate::kFEC_3_4 == rate) && (caps & FE_CAN_FEC_3_4)) || 00458 ((DTVCodeRate::kFEC_4_5 == rate) && (caps & FE_CAN_FEC_4_5)) || 00459 ((DTVCodeRate::kFEC_5_6 == rate) && (caps & FE_CAN_FEC_5_6)) || 00460 ((DTVCodeRate::kFEC_6_7 == rate) && (caps & FE_CAN_FEC_6_7)) || 00461 ((DTVCodeRate::kFEC_7_8 == rate) && (caps & FE_CAN_FEC_7_8)) || 00462 ((DTVCodeRate::kFEC_8_9 == rate) && (caps & FE_CAN_FEC_8_9)) || 00463 ((DTVCodeRate::kFECAuto == rate) && (caps & FE_CAN_FEC_AUTO)); 00464 } 00465 00469 bool DVBChannel::CheckModulation(DTVModulation modulation) const 00470 { 00471 const DTVModulation m = modulation; 00472 const uint64_t c = capabilities; 00473 00474 return 00475 ((DTVModulation::kModulationQPSK == m) && (c & FE_CAN_QPSK)) || 00476 #if HAVE_FE_CAN_2G_MODULATION 00477 ((DTVModulation::kModulation8PSK == m) && (c & FE_CAN_2G_MODULATION)) || 00478 #endif //HAVE_FE_CAN_2G_MODULATION 00479 ((DTVModulation::kModulationQAM16 == m) && (c & FE_CAN_QAM_16)) || 00480 ((DTVModulation::kModulationQAM32 == m) && (c & FE_CAN_QAM_32)) || 00481 ((DTVModulation::kModulationQAM64 == m) && (c & FE_CAN_QAM_64)) || 00482 ((DTVModulation::kModulationQAM128 == m) && (c & FE_CAN_QAM_128)) || 00483 ((DTVModulation::kModulationQAM256 == m) && (c & FE_CAN_QAM_256)) || 00484 ((DTVModulation::kModulationQAMAuto == m) && (c & FE_CAN_QAM_AUTO)) || 00485 ((DTVModulation::kModulation8VSB == m) && (c & FE_CAN_8VSB)) || 00486 ((DTVModulation::kModulation16VSB == m) && (c & FE_CAN_16VSB)); 00487 } 00488 00492 void DVBChannel::SetPMT(const ProgramMapTable *pmt) 00493 { 00494 if (pmt && dvbcam->IsRunning()) 00495 dvbcam->SetPMT(this, pmt); 00496 } 00497 00502 void DVBChannel::SetTimeOffset(double offset) 00503 { 00504 if (dvbcam->IsRunning()) 00505 dvbcam->SetTimeOffset(offset); 00506 } 00507 00508 00509 bool DVBChannel::Tune(const DTVMultiplex &tuning, QString inputname) 00510 { 00511 int inputid = inputname.isEmpty() ? 00512 m_currentInputID : GetInputByName(inputname); 00513 if (inputid < 0) 00514 { 00515 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Tune(): Invalid input '%1'.") 00516 .arg(inputname)); 00517 return false; 00518 } 00519 return Tune(tuning, inputid, false, false); 00520 } 00521 00522 #if DVB_API_VERSION >= 5 00523 static struct dtv_properties *dtvmultiplex_to_dtvproperties( 00524 DTVTunerType tuner_type, const DTVMultiplex &tuning, int intermediate_freq, 00525 bool can_fec_auto, bool do_tune = true) 00526 { 00527 uint c = 0; 00528 struct dtv_properties *cmdseq; 00529 00530 if (tuner_type != DTVTunerType::kTunerTypeDVBT && 00531 tuner_type != DTVTunerType::kTunerTypeDVBC && 00532 tuner_type != DTVTunerType::kTunerTypeDVBS1 && 00533 tuner_type != DTVTunerType::kTunerTypeDVBS2) 00534 { 00535 LOG(VB_GENERAL, LOG_ERR, "DVBChan: Unsupported tuner type " + 00536 tuner_type.toString()); 00537 return NULL; 00538 } 00539 00540 cmdseq = (struct dtv_properties*) calloc(1, sizeof(*cmdseq)); 00541 if (!cmdseq) 00542 return NULL; 00543 00544 cmdseq->props = (struct dtv_property*) calloc(11, sizeof(*(cmdseq->props))); 00545 if (!(cmdseq->props)) 00546 { 00547 free(cmdseq); 00548 return NULL; 00549 } 00550 00551 // The cx24116 DVB-S2 demod anounce FE_CAN_FEC_AUTO but has apparently 00552 // trouble with FEC_AUTO on DVB-S2 transponders 00553 if (tuning.mod_sys == DTVModulationSystem::kModulationSystem_DVBS2) 00554 can_fec_auto = false; 00555 00556 if (tuner_type == DTVTunerType::kTunerTypeDVBS2) 00557 { 00558 cmdseq->props[c].cmd = DTV_DELIVERY_SYSTEM; 00559 cmdseq->props[c++].u.data = tuning.mod_sys; 00560 } 00561 00562 cmdseq->props[c].cmd = DTV_FREQUENCY; 00563 cmdseq->props[c++].u.data = intermediate_freq ? intermediate_freq : tuning.frequency; 00564 cmdseq->props[c].cmd = DTV_MODULATION; 00565 cmdseq->props[c++].u.data = tuning.modulation; 00566 cmdseq->props[c].cmd = DTV_INVERSION; 00567 cmdseq->props[c++].u.data = tuning.inversion; 00568 00569 if (tuner_type == DTVTunerType::kTunerTypeDVBS1 || 00570 tuner_type == DTVTunerType::kTunerTypeDVBS2 || 00571 tuner_type == DTVTunerType::kTunerTypeDVBC) 00572 { 00573 cmdseq->props[c].cmd = DTV_SYMBOL_RATE; 00574 cmdseq->props[c++].u.data = tuning.symbolrate; 00575 } 00576 00577 if (tuner_type.IsFECVariable()) 00578 { 00579 cmdseq->props[c].cmd = DTV_INNER_FEC; 00580 cmdseq->props[c++].u.data = can_fec_auto ? FEC_AUTO : tuning.fec; 00581 } 00582 00583 if (tuner_type == DTVTunerType::kTunerTypeDVBT) 00584 { 00585 cmdseq->props[c].cmd = DTV_BANDWIDTH_HZ; 00586 cmdseq->props[c++].u.data = (8-tuning.bandwidth) * 1000000; 00587 cmdseq->props[c].cmd = DTV_CODE_RATE_HP; 00588 cmdseq->props[c++].u.data = tuning.hp_code_rate; 00589 cmdseq->props[c].cmd = DTV_CODE_RATE_LP; 00590 cmdseq->props[c++].u.data = tuning.lp_code_rate; 00591 cmdseq->props[c].cmd = DTV_TRANSMISSION_MODE; 00592 cmdseq->props[c++].u.data = tuning.trans_mode; 00593 cmdseq->props[c].cmd = DTV_GUARD_INTERVAL; 00594 cmdseq->props[c++].u.data = tuning.guard_interval; 00595 cmdseq->props[c].cmd = DTV_HIERARCHY; 00596 cmdseq->props[c++].u.data = tuning.hierarchy; 00597 } 00598 00599 if (tuning.mod_sys == DTVModulationSystem::kModulationSystem_DVBS2) 00600 { 00601 cmdseq->props[c].cmd = DTV_PILOT; 00602 cmdseq->props[c++].u.data = PILOT_AUTO; 00603 cmdseq->props[c].cmd = DTV_ROLLOFF; 00604 cmdseq->props[c++].u.data = tuning.rolloff; 00605 } 00606 else if (tuning.mod_sys == DTVModulationSystem::kModulationSystem_DVBS) 00607 { 00608 cmdseq->props[c].cmd = DTV_ROLLOFF; 00609 cmdseq->props[c++].u.data = DTVRollOff::kRollOff_35; 00610 } 00611 00612 if (do_tune) 00613 cmdseq->props[c++].cmd = DTV_TUNE; 00614 00615 cmdseq->num = c; 00616 00617 return cmdseq; 00618 } 00619 #endif 00620 00621 00622 /***************************************************************************** 00623 Tuning functions for each of the five types of cards. 00624 *****************************************************************************/ 00625 00638 bool DVBChannel::Tune(const DTVMultiplex &tuning, 00639 uint inputid, 00640 bool force_reset, 00641 bool same_input) 00642 { 00643 QMutexLocker lock(&tune_lock); 00644 QMutexLocker locker(&hw_lock); 00645 00646 DVBChannel *master = GetMasterLock(); 00647 if (master != this) 00648 { 00649 LOG(VB_CHANNEL, LOG_INFO, LOC + "tuning on slave channel"); 00650 SetSIStandard(tuning.sistandard); 00651 bool ok = master->Tune(tuning, inputid, force_reset, false); 00652 ReturnMasterLock(master); 00653 return ok; 00654 } 00655 ReturnMasterLock(master); // if we're the master we don't need this lock.. 00656 00657 00658 int intermediate_freq = 0; 00659 bool can_fec_auto = false; 00660 bool reset = (force_reset || first_tune); 00661 00662 if (tunerType.IsDiSEqCSupported() && !diseqc_tree) 00663 { 00664 LOG(VB_GENERAL, LOG_ERR, LOC + 00665 "DVB-S needs device tree for LNB handling"); 00666 return false; 00667 } 00668 00669 desired_tuning = tuning; 00670 00671 if (fd_frontend < 0) 00672 { 00673 LOG(VB_GENERAL, LOG_ERR, LOC + "Tune(): Card not open!"); 00674 00675 return false; 00676 } 00677 00678 // Remove any events in queue before tuning. 00679 drain_dvb_events(fd_frontend); 00680 00681 // send DVB-S setup 00682 if (diseqc_tree) 00683 { 00684 // configure for new input 00685 if (!same_input) 00686 diseqc_settings.Load(inputid); 00687 00688 // execute diseqc commands 00689 if (!diseqc_tree->Execute(diseqc_settings, tuning)) 00690 { 00691 LOG(VB_GENERAL, LOG_ERR, LOC + 00692 "Tune(): Failed to setup DiSEqC devices"); 00693 return false; 00694 } 00695 00696 // retrieve actual intermediate frequency 00697 DiSEqCDevLNB *lnb = diseqc_tree->FindLNB(diseqc_settings); 00698 if (!lnb) 00699 { 00700 LOG(VB_GENERAL, LOG_ERR, LOC + 00701 "Tune(): No LNB for this configuration"); 00702 return false; 00703 } 00704 00705 if (lnb->GetDeviceID() != last_lnb_dev_id) 00706 { 00707 last_lnb_dev_id = lnb->GetDeviceID(); 00708 // make sure we tune to frequency, if the lnb has changed 00709 reset = first_tune = true; 00710 } 00711 00712 intermediate_freq = lnb->GetIntermediateFrequency( 00713 diseqc_settings, tuning); 00714 00715 // if card can auto-FEC, use it -- sometimes NITs are inaccurate 00716 if (capabilities & FE_CAN_FEC_AUTO) 00717 can_fec_auto = true; 00718 00719 // Check DVB-S intermediate frequency here since it requires a fully 00720 // initialized diseqc tree 00721 CheckFrequency(intermediate_freq); 00722 } 00723 00724 LOG(VB_CHANNEL, LOG_INFO, LOC + "Old Params: " + prev_tuning.toString() + 00725 "\n\t\t\t" + LOC + "New Params: " + tuning.toString()); 00726 00727 // DVB-S is in kHz, other DVB is in Hz 00728 bool is_dvbs = ((DTVTunerType::kTunerTypeDVBS1 == tunerType) || 00729 (DTVTunerType::kTunerTypeDVBS2 == tunerType)); 00730 int freq_mult = (is_dvbs) ? 1 : 1000; 00731 QString suffix = (is_dvbs) ? "kHz" : "Hz"; 00732 00733 if (reset || !prev_tuning.IsEqual(tunerType, tuning, 500 * freq_mult)) 00734 { 00735 LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Tune(): Tuning to %1%2") 00736 .arg(intermediate_freq ? intermediate_freq : tuning.frequency) 00737 .arg(suffix)); 00738 00739 #if DVB_API_VERSION >=5 00740 if (DTVTunerType::kTunerTypeDVBS2 == tunerType) 00741 { 00742 struct dtv_property p_clear; 00743 struct dtv_properties cmdseq_clear; 00744 00745 p_clear.cmd = DTV_CLEAR; 00746 cmdseq_clear.num = 1; 00747 cmdseq_clear.props = &p_clear; 00748 00749 if ((ioctl(fd_frontend, FE_SET_PROPERTY, &cmdseq_clear)) < 0) 00750 { 00751 LOG(VB_GENERAL, LOG_ERR, LOC + 00752 "Tune(): Clearing DTV properties cache failed." + ENO); 00753 return false; 00754 } 00755 00756 struct dtv_properties *cmds = dtvmultiplex_to_dtvproperties( 00757 tunerType, tuning, intermediate_freq, can_fec_auto); 00758 00759 if (!cmds) { 00760 LOG(VB_GENERAL, LOG_ERR, LOC + 00761 "Failed to convert DTVMultiplex to DTV_PROPERTY sequence"); 00762 return false; 00763 } 00764 00765 if (VERBOSE_LEVEL_CHECK(VB_CHANNEL, LOG_DEBUG)) 00766 { 00767 for (uint i = 0; i < cmds->num; i++) 00768 { 00769 LOG(VB_CHANNEL, LOG_DEBUG, LOC + 00770 QString("prop %1: cmd = %2, data %3") 00771 .arg(i).arg(cmds->props[i].cmd) 00772 .arg(cmds->props[i].u.data)); 00773 } 00774 } 00775 00776 int res = ioctl(fd_frontend, FE_SET_PROPERTY, cmds); 00777 00778 free(cmds->props); 00779 free(cmds); 00780 00781 if (res < 0) 00782 { 00783 LOG(VB_GENERAL, LOG_ERR, LOC + 00784 "Tune(): Setting Frontend tuning parameters failed." + ENO); 00785 return false; 00786 } 00787 } 00788 else 00789 #endif 00790 { 00791 struct dvb_frontend_parameters params = dtvmultiplex_to_dvbparams( 00792 tunerType, tuning, intermediate_freq, can_fec_auto); 00793 00794 if (ioctl(fd_frontend, FE_SET_FRONTEND, ¶ms) < 0) 00795 { 00796 LOG(VB_GENERAL, LOG_ERR, LOC + 00797 "Tune(): Setting Frontend tuning parameters failed." + ENO); 00798 return false; 00799 } 00800 } 00801 00802 // Extra delay to add for broken DVB drivers 00803 if (tuning_delay) 00804 usleep(tuning_delay * 1000); 00805 00806 wait_for_backend(fd_frontend, 5 /* msec */); 00807 00808 prev_tuning = tuning; 00809 first_tune = false; 00810 } 00811 00812 SetSIStandard(tuning.sistandard); 00813 00814 LOG(VB_CHANNEL, LOG_INFO, LOC + "Tune(): Frequency tuning successful."); 00815 00816 return true; 00817 } 00818 00819 bool DVBChannel::Retune(void) 00820 { 00821 return Tune(desired_tuning, m_currentInputID, true, true); 00822 } 00823 00824 QString DVBChannel::GetFrontendName(void) const 00825 { 00826 QString tmp = frontend_name; 00827 tmp.detach(); 00828 return tmp; 00829 } 00830 00834 bool DVBChannel::IsTuningParamsProbeSupported(void) const 00835 { 00836 QMutexLocker locker(&hw_lock); 00837 00838 if (fd_frontend < 0) 00839 { 00840 LOG(VB_GENERAL, LOG_ERR, LOC + "Card not open!"); 00841 00842 return false; 00843 } 00844 00845 const DVBChannel *master = GetMasterLock(); 00846 if (master != this) 00847 { 00848 bool ok = master->IsTuningParamsProbeSupported(); 00849 ReturnMasterLock(master); 00850 return ok; 00851 } 00852 ReturnMasterLock(master); // if we're the master we don't need this lock.. 00853 00854 if (diseqc_tree) 00855 { 00856 // TODO We need to implement the inverse of 00857 // lnb->GetIntermediateFrequency() for ProbeTuningParams() 00858 // to accurately reflect the frequency before LNB transform. 00859 return false; 00860 } 00861 00862 dvb_frontend_parameters params; 00863 00864 int res = ioctl(fd_frontend, FE_GET_FRONTEND, ¶ms); 00865 if (res < 0) 00866 { 00867 LOG(VB_CHANNEL, LOG_ERR, LOC + "Getting device frontend failed." + ENO); 00868 } 00869 00870 return (res >= 0); 00871 } 00872 00880 bool DVBChannel::ProbeTuningParams(DTVMultiplex &tuning) const 00881 { 00882 QMutexLocker locker(&hw_lock); 00883 00884 if (fd_frontend < 0) 00885 { 00886 LOG(VB_GENERAL, LOG_ERR, LOC + "Card not open!"); 00887 00888 return false; 00889 } 00890 00891 const DVBChannel *master = GetMasterLock(); 00892 if (master != this) 00893 { 00894 bool ok = master->ProbeTuningParams(tuning); 00895 ReturnMasterLock(master); 00896 return ok; 00897 } 00898 ReturnMasterLock(master); // if we're the master we don't need this lock.. 00899 00900 if (diseqc_tree) 00901 { 00902 // TODO We need to implement the inverse of 00903 // lnb->GetIntermediateFrequency() for ProbeTuningParams() 00904 // to accurately reflect the frequency before LNB transform. 00905 return false; 00906 } 00907 00908 if (tunerType == DTVTunerType::kTunerTypeDVBS2) 00909 { 00910 // TODO implement probing of tuning parameters with FE_GET_PROPERTY 00911 return false; 00912 } 00913 00914 dvb_frontend_parameters params; 00915 if (ioctl(fd_frontend, FE_GET_FRONTEND, ¶ms) < 0) 00916 { 00917 LOG(VB_GENERAL, LOG_ERR, LOC + 00918 "Getting Frontend tuning parameters failed." + ENO); 00919 00920 return false; 00921 } 00922 00923 uint mplex = tuning.mplex; 00924 QString sistandard = tuning.sistandard; sistandard.detach(); 00925 00926 tuning = dvbparams_to_dtvmultiplex(tunerType, params); 00927 00928 tuning.mplex = mplex; 00929 tuning.sistandard = sistandard; 00930 00931 return true; 00932 } 00933 00938 int DVBChannel::GetChanID() const 00939 { 00940 int cardid = GetCardID(); 00941 00942 MSqlQuery query(MSqlQuery::InitCon()); 00943 00944 query.prepare("SELECT chanid " 00945 "FROM channel, cardinput " 00946 "WHERE cardinput.sourceid = channel.sourceid AND " 00947 " channel.channum = :CHANNUM AND " 00948 " cardinput.cardid = :CARDID"); 00949 00950 query.bindValue(":CHANNUM", m_curchannelname); 00951 query.bindValue(":CARDID", cardid); 00952 00953 if (!query.exec() || !query.isActive()) 00954 { 00955 MythDB::DBError("fetching chanid", query); 00956 return -1; 00957 } 00958 00959 if (!query.next()) 00960 return -1; 00961 00962 return query.value(0).toInt(); 00963 } 00964 00965 const DiSEqCDevRotor *DVBChannel::GetRotor(void) const 00966 { 00967 if (diseqc_tree) 00968 return diseqc_tree->FindRotor(diseqc_settings); 00969 00970 return NULL; 00971 } 00972 00973 // documented in dvbchannel.h 00974 bool DVBChannel::HasLock(bool *ok) const 00975 { 00976 const DVBChannel *master = GetMasterLock(); 00977 if (master != this) 00978 { 00979 bool haslock = master->HasLock(ok); 00980 ReturnMasterLock(master); 00981 return haslock; 00982 } 00983 ReturnMasterLock(master); // if we're the master we don't need this lock.. 00984 00985 fe_status_t status; 00986 memset(&status, 0, sizeof(status)); 00987 00988 int ret = ioctl(fd_frontend, FE_READ_STATUS, &status); 00989 if (ret < 0) 00990 { 00991 LOG(VB_GENERAL, LOG_ERR, LOC + 00992 "Getting Frontend status failed." + ENO); 00993 } 00994 00995 if (ok) 00996 *ok = (0 == ret); 00997 00998 return status & FE_HAS_LOCK; 00999 } 01000 01001 // documented in dvbchannel.h 01002 double DVBChannel::GetSignalStrength(bool *ok) const 01003 { 01004 const DVBChannel *master = GetMasterLock(); 01005 if (master != this) 01006 { 01007 double val = master->GetSignalStrength(ok); 01008 ReturnMasterLock(master); 01009 return val; 01010 } 01011 ReturnMasterLock(master); // if we're the master we don't need this lock.. 01012 01013 // We use uint16_t for sig because this is correct for DVB API 4.0, 01014 // and works better than the correct int16_t for the 3.x API 01015 uint16_t sig = 0; 01016 01017 int ret = ioctl(fd_frontend, FE_READ_SIGNAL_STRENGTH, &sig); 01018 if (ret < 0) 01019 { 01020 LOG(VB_GENERAL, LOG_ERR, LOC + 01021 "Getting Frontend signal strength failed." + ENO); 01022 } 01023 01024 if (ok) 01025 *ok = (0 == ret); 01026 01027 return sig * (1.0 / 65535.0); 01028 } 01029 01030 // documented in dvbchannel.h 01031 double DVBChannel::GetSNR(bool *ok) const 01032 { 01033 const DVBChannel *master = GetMasterLock(); 01034 if (master != this) 01035 { 01036 double val = master->GetSNR(ok); 01037 ReturnMasterLock(master); 01038 return val; 01039 } 01040 ReturnMasterLock(master); // if we're the master we don't need this lock.. 01041 01042 // We use uint16_t for sig because this is correct for DVB API 4.0, 01043 // and works better than the correct int16_t for the 3.x API 01044 01045 uint16_t snr = 0; 01046 int ret = ioctl(fd_frontend, FE_READ_SNR, &snr); 01047 if (ret < 0) 01048 { 01049 LOG(VB_GENERAL, LOG_ERR, LOC + 01050 "Getting Frontend signal/noise ratio failed." + ENO); 01051 } 01052 01053 if (ok) 01054 *ok = (0 == ret); 01055 01056 return snr * (1.0 / 65535.0); 01057 } 01058 01059 // documented in dvbchannel.h 01060 double DVBChannel::GetBitErrorRate(bool *ok) const 01061 { 01062 const DVBChannel *master = GetMasterLock(); 01063 if (master != this) 01064 { 01065 double val = master->GetBitErrorRate(ok); 01066 ReturnMasterLock(master); 01067 return val; 01068 } 01069 ReturnMasterLock(master); // if we're the master we don't need this lock.. 01070 01071 uint32_t ber = 0; 01072 int ret = ioctl(fd_frontend, FE_READ_BER, &ber); 01073 if (ret < 0) 01074 { 01075 LOG(VB_GENERAL, LOG_ERR, LOC + 01076 "Getting Frontend signal error rate failed." + ENO); 01077 } 01078 01079 if (ok) 01080 *ok = (0 == ret); 01081 01082 return (double) ber; 01083 } 01084 01085 // documented in dvbchannel.h 01086 double DVBChannel::GetUncorrectedBlockCount(bool *ok) const 01087 { 01088 const DVBChannel *master = GetMasterLock(); 01089 if (master != this) 01090 { 01091 double val = master->GetUncorrectedBlockCount(ok); 01092 ReturnMasterLock(master); 01093 return val; 01094 } 01095 ReturnMasterLock(master); // if we're the master we don't need this lock.. 01096 01097 uint32_t ublocks = 0; 01098 int ret = ioctl(fd_frontend, FE_READ_UNCORRECTED_BLOCKS, &ublocks); 01099 if (ret < 0) 01100 { 01101 LOG(VB_GENERAL, LOG_ERR, LOC + 01102 "Getting Frontend uncorrected block count failed." + ENO); 01103 } 01104 01105 if (ok) 01106 *ok = (0 == ret); 01107 01108 return (double) ublocks; 01109 } 01110 01111 DVBChannel *DVBChannel::GetMasterLock(void) 01112 { 01113 QString devname = CardUtil::GetDeviceName(DVB_DEV_FRONTEND, device); 01114 DTVChannel *master = DTVChannel::GetMasterLock(devname); 01115 DVBChannel *dvbm = dynamic_cast<DVBChannel*>(master); 01116 if (master && !dvbm) 01117 DTVChannel::ReturnMasterLock(master); 01118 return dvbm; 01119 } 01120 01121 void DVBChannel::ReturnMasterLock(DVBChannelP &dvbm) 01122 { 01123 DTVChannel *chan = static_cast<DTVChannel*>(dvbm); 01124 DTVChannel::ReturnMasterLock(chan); 01125 dvbm = NULL; 01126 } 01127 01128 const DVBChannel *DVBChannel::GetMasterLock(void) const 01129 { 01130 QString devname = CardUtil::GetDeviceName(DVB_DEV_FRONTEND, device); 01131 DTVChannel *master = DTVChannel::GetMasterLock(devname); 01132 DVBChannel *dvbm = dynamic_cast<DVBChannel*>(master); 01133 if (master && !dvbm) 01134 DTVChannel::ReturnMasterLock(master); 01135 return dvbm; 01136 } 01137 01138 void DVBChannel::ReturnMasterLock(DVBChannelCP &dvbm) 01139 { 01140 DTVChannel *chan = 01141 static_cast<DTVChannel*>(const_cast<DVBChannel*>(dvbm)); 01142 DTVChannel::ReturnMasterLock(chan); 01143 dvbm = NULL; 01144 } 01145 01146 bool DVBChannel::IsMaster(void) const 01147 { 01148 const DVBChannel *master = GetMasterLock(); 01149 bool is_master = (master == this); 01150 ReturnMasterLock(master); 01151 return is_master; 01152 } 01153 01158 static void drain_dvb_events(int fd) 01159 { 01160 struct dvb_frontend_event event; 01161 int ret = 0; 01162 while ((ret = ioctl(fd, FE_GET_EVENT, &event)) == 0); 01163 if (ret < 0) 01164 { 01165 LOG(VB_CHANNEL, LOG_DEBUG, "Draining DVB Event failed. " + ENO); 01166 } 01167 } 01168 01192 static bool wait_for_backend(int fd, int timeout_ms) 01193 { 01194 struct timeval select_timeout = { 0, (timeout_ms % 1000) * 1000 /*usec*/}; 01195 fd_set fd_select_set; 01196 FD_ZERO( &fd_select_set); 01197 FD_SET (fd, &fd_select_set); 01198 01199 // Try to wait for some output like an event, unfortunately 01200 // this fails on several DVB cards, so we have a timeout. 01201 int ret = 0; 01202 do ret = select(fd+1, &fd_select_set, NULL, NULL, &select_timeout); 01203 while ((-1 == ret) && (EINTR == errno)); 01204 01205 if (-1 == ret) 01206 { 01207 LOG(VB_GENERAL, LOG_ERR, 01208 "DVBChan: wait_for_backend: Failed to wait on output" + ENO); 01209 01210 return false; 01211 } 01212 01213 // This is supposed to work on all cards, post 2.6.12... 01214 fe_status_t status; 01215 if (ioctl(fd, FE_READ_STATUS, &status) < 0) 01216 { 01217 LOG(VB_GENERAL, LOG_ERR, 01218 "DVBChan: wait_for_backend: Failed to get status" + ENO); 01219 01220 return false; 01221 } 01222 01223 LOG(VB_CHANNEL, LOG_INFO, QString("DVBChan: wait_for_backend: Status: %1") 01224 .arg(toString(status))); 01225 01226 return true; 01227 } 01228 01229 static struct dvb_frontend_parameters dtvmultiplex_to_dvbparams( 01230 DTVTunerType tuner_type, const DTVMultiplex &tuning, 01231 int intermediate_freq, bool can_fec_auto) 01232 { 01233 dvb_frontend_parameters params; 01234 memset(¶ms, 0, sizeof(params)); 01235 01236 params.frequency = tuning.frequency; 01237 params.inversion = (fe_spectral_inversion_t) (int) tuning.inversion; 01238 01239 if (DTVTunerType::kTunerTypeDVBS1 == tuner_type) 01240 { 01241 if (tuning.mod_sys == DTVModulationSystem::kModulationSystem_DVBS2) 01242 LOG(VB_GENERAL, LOG_ERR, 01243 "DVBChan: Error, Tuning of a DVB-S2 transport " 01244 "with a DVB-S card will fail."); 01245 01246 params.frequency = intermediate_freq; 01247 params.u.qpsk.symbol_rate = tuning.symbolrate; 01248 params.u.qpsk.fec_inner = can_fec_auto ? FEC_AUTO 01249 : (fe_code_rate_t) (int) tuning.fec; 01250 } 01251 01252 if (DTVTunerType::kTunerTypeDVBS2 == tuner_type) 01253 { 01254 LOG(VB_GENERAL, LOG_ERR, 01255 "DVBChan: Error, MythTV was compiled without " 01256 "DVB-S2 headers being present so DVB-S2 tuning will fail."); 01257 } 01258 01259 if (DTVTunerType::kTunerTypeDVBC == tuner_type) 01260 { 01261 params.u.qam.symbol_rate = tuning.symbolrate; 01262 params.u.qam.fec_inner = (fe_code_rate_t) (int) tuning.fec; 01263 params.u.qam.modulation = (fe_modulation_t) (int) tuning.modulation; 01264 } 01265 01266 if (DTVTunerType::kTunerTypeDVBT == tuner_type) 01267 { 01268 params.u.ofdm.bandwidth = 01269 (fe_bandwidth_t) (int) tuning.bandwidth; 01270 params.u.ofdm.code_rate_HP = 01271 (fe_code_rate_t) (int) tuning.hp_code_rate; 01272 params.u.ofdm.code_rate_LP = 01273 (fe_code_rate_t) (int) tuning.lp_code_rate; 01274 params.u.ofdm.constellation = 01275 (fe_modulation_t) (int) tuning.modulation; 01276 params.u.ofdm.transmission_mode = 01277 (fe_transmit_mode_t) (int) tuning.trans_mode; 01278 params.u.ofdm.guard_interval = 01279 (fe_guard_interval_t) (int) tuning.guard_interval; 01280 params.u.ofdm.hierarchy_information = 01281 (fe_hierarchy_t) (int) tuning.hierarchy; 01282 } 01283 01284 if (DTVTunerType::kTunerTypeATSC == tuner_type) 01285 { 01286 params.u.vsb.modulation = 01287 (fe_modulation_t) (int) tuning.modulation; 01288 } 01289 01290 return params; 01291 } 01292 01293 static DTVMultiplex dvbparams_to_dtvmultiplex( 01294 DTVTunerType tuner_type, const dvb_frontend_parameters ¶ms) 01295 { 01296 DTVMultiplex tuning; 01297 01298 tuning.frequency = params.frequency; 01299 tuning.inversion = params.inversion; 01300 01301 if ((DTVTunerType::kTunerTypeDVBS1 == tuner_type) || 01302 (DTVTunerType::kTunerTypeDVBS2 == tuner_type)) 01303 { 01304 tuning.symbolrate = params.u.qpsk.symbol_rate; 01305 tuning.fec = params.u.qpsk.fec_inner; 01306 } 01307 01308 if (DTVTunerType::kTunerTypeDVBC == tuner_type) 01309 { 01310 tuning.symbolrate = params.u.qam.symbol_rate; 01311 tuning.fec = params.u.qam.fec_inner; 01312 tuning.modulation = params.u.qam.modulation; 01313 } 01314 01315 if (DTVTunerType::kTunerTypeDVBT == tuner_type) 01316 { 01317 tuning.bandwidth = params.u.ofdm.bandwidth; 01318 tuning.hp_code_rate = params.u.ofdm.code_rate_HP; 01319 tuning.lp_code_rate = params.u.ofdm.code_rate_LP; 01320 tuning.modulation = params.u.ofdm.constellation; 01321 tuning.trans_mode = params.u.ofdm.transmission_mode; 01322 tuning.guard_interval = params.u.ofdm.guard_interval; 01323 tuning.hierarchy = params.u.ofdm.hierarchy_information; 01324 } 01325 01326 if (DTVTunerType::kTunerTypeATSC == tuner_type) 01327 { 01328 tuning.modulation = params.u.vsb.modulation; 01329 } 01330 01331 return tuning; 01332 }
1.7.6.1