|
MythTV
0.26-pre
|
00001 00008 // POSIX headers 00009 #include <fcntl.h> 00010 #include <unistd.h> 00011 #ifndef USING_MINGW 00012 #include <sys/select.h> 00013 #include <sys/ioctl.h> 00014 #endif 00015 00016 // Qt headers 00017 #include <QCoreApplication> 00018 #include <QBuffer> 00019 #include <QFile> 00020 #include <QHttp> 00021 #include <QUrl> 00022 00023 // MythTV headers 00024 #include "cetonstreamhandler.h" 00025 #include "streamlisteners.h" 00026 #include "mpegstreamdata.h" 00027 #include "cetonchannel.h" 00028 #include "mythlogging.h" 00029 #include "cetonrtp.h" 00030 #include "cardutil.h" 00031 00032 #define LOC QString("CetonSH(%1): ").arg(_device) 00033 00034 QMap<QString,CetonStreamHandler*> CetonStreamHandler::_handlers; 00035 QMap<QString,uint> CetonStreamHandler::_handlers_refcnt; 00036 QMutex CetonStreamHandler::_handlers_lock; 00037 QMap<QString, bool> CetonStreamHandler::_info_queried; 00038 00039 CetonStreamHandler *CetonStreamHandler::Get(const QString &devname) 00040 { 00041 QMutexLocker locker(&_handlers_lock); 00042 00043 QString devkey = devname.toUpper(); 00044 00045 QMap<QString,CetonStreamHandler*>::iterator it = _handlers.find(devkey); 00046 00047 if (it == _handlers.end()) 00048 { 00049 CetonStreamHandler *newhandler = new CetonStreamHandler(devkey); 00050 newhandler->Open(); 00051 _handlers[devkey] = newhandler; 00052 _handlers_refcnt[devkey] = 1; 00053 00054 LOG(VB_RECORD, LOG_INFO, 00055 QString("CetonSH: Creating new stream handler %1 for %2") 00056 .arg(devkey).arg(devname)); 00057 } 00058 else 00059 { 00060 _handlers_refcnt[devkey]++; 00061 uint rcount = _handlers_refcnt[devkey]; 00062 LOG(VB_RECORD, LOG_INFO, 00063 QString("CetonSH: Using existing stream handler %1 for %2") 00064 .arg(devkey) 00065 .arg(devname) + QString(" (%1 in use)").arg(rcount)); 00066 } 00067 00068 return _handlers[devkey]; 00069 } 00070 00071 void CetonStreamHandler::Return(CetonStreamHandler * & ref) 00072 { 00073 QMutexLocker locker(&_handlers_lock); 00074 00075 QString devname = ref->_device; 00076 00077 QMap<QString,uint>::iterator rit = _handlers_refcnt.find(devname); 00078 if (rit == _handlers_refcnt.end()) 00079 return; 00080 00081 if (*rit > 1) 00082 { 00083 ref = NULL; 00084 (*rit)--; 00085 return; 00086 } 00087 00088 QMap<QString,CetonStreamHandler*>::iterator it = _handlers.find(devname); 00089 if ((it != _handlers.end()) && (*it == ref)) 00090 { 00091 LOG(VB_RECORD, LOG_INFO, QString("CetonSH: Closing handler for %1") 00092 .arg(devname)); 00093 ref->Close(); 00094 delete *it; 00095 _handlers.erase(it); 00096 } 00097 else 00098 { 00099 LOG(VB_GENERAL, LOG_ERR, 00100 QString("CetonSH Error: Couldn't find handler for %1") 00101 .arg(devname)); 00102 } 00103 00104 _handlers_refcnt.erase(rit); 00105 ref = NULL; 00106 } 00107 00108 CetonStreamHandler::CetonStreamHandler(const QString &device) : 00109 StreamHandler(device), 00110 _connected(false), 00111 _valid(false) 00112 { 00113 setObjectName("CetonStreamHandler"); 00114 00115 QStringList parts = device.split("-"); 00116 if (parts.size() != 2) 00117 { 00118 LOG(VB_GENERAL, LOG_ERR, LOC + 00119 QString("Invalid device id %1").arg(_device)); 00120 return; 00121 } 00122 _ip_address = parts.at(0); 00123 00124 QStringList tuner_parts = parts.at(1).split("."); 00125 if (tuner_parts.size() == 2) 00126 { 00127 _using_rtp = (tuner_parts.at(0) == "RTP"); 00128 _card = tuner_parts.at(0).toUInt(); 00129 _tuner = tuner_parts.at(1).toUInt(); 00130 } 00131 else 00132 { 00133 LOG(VB_GENERAL, LOG_ERR, LOC + 00134 QString("Invalid device id %1").arg(_device)); 00135 return; 00136 } 00137 00138 if (GetVar("diag", "Host_IP_Address") == "") 00139 { 00140 LOG(VB_GENERAL, LOG_ERR, LOC + 00141 "Ceton tuner does not seem to be available at IP"); 00142 return; 00143 } 00144 00145 if (!_using_rtp) 00146 { 00147 _device_path = QString("/dev/ceton/ctn91xx_mpeg%1_%2") 00148 .arg(_card).arg(_tuner); 00149 00150 if (!QFile(_device_path).exists()) 00151 { 00152 LOG(VB_GENERAL, LOG_ERR, LOC + "Tuner device unavailable"); 00153 return; 00154 } 00155 } 00156 00157 _valid = true; 00158 00159 QString cardstatus = GetVar("cas", "CardStatus"); 00160 _using_cablecard = cardstatus == "Inserted"; 00161 00162 if (!_info_queried.contains(_ip_address)) 00163 { 00164 QString sernum = GetVar("diag", "Host_Serial_Number"); 00165 QString firmware_ver = GetVar("diag", "Host_Firmware"); 00166 QString hardware_ver = GetVar("diag", "Hardware_Revision"); 00167 00168 LOG(VB_RECORD, LOG_INFO, LOC + 00169 QString("Ceton device %1 initialized. SN: %2, " 00170 "Firmware ver. %3, Hardware ver. %4") 00171 .arg(_ip_address).arg(sernum).arg(firmware_ver).arg(hardware_ver)); 00172 00173 if (_using_cablecard) 00174 { 00175 QString brand = GetVar("cas", "CardManufacturer"); 00176 QString auth = GetVar("cas", "CardAuthorization"); 00177 00178 LOG(VB_RECORD, LOG_INFO, LOC + 00179 QString("Cable card installed (%1) - %2").arg(brand).arg(auth)); 00180 } 00181 else 00182 { 00183 LOG(VB_RECORD, LOG_INFO, LOC + 00184 "Cable card NOT installed (operating in QAM tuner mode)"); 00185 } 00186 00187 _info_queried.insert(_ip_address, true); 00188 } 00189 } 00190 00191 void CetonStreamHandler::run(void) 00192 { 00193 RunProlog(); 00194 00195 QFile file(_device_path); 00196 CetonRTP rtp(_ip_address, _tuner); 00197 00198 if (_using_rtp) 00199 { 00200 if (!(rtp.Init() && rtp.StartStreaming())) 00201 { 00202 LOG(VB_GENERAL, LOG_ERR, LOC + 00203 "Starting recording (RTP initialization failed). Aborting."); 00204 _error = true; 00205 } 00206 } 00207 else 00208 { 00209 if (!file.open(QIODevice::ReadOnly)) 00210 { 00211 LOG(VB_GENERAL, LOG_ERR, LOC + 00212 "Starting recording (file open failed). Aborting."); 00213 _error = true; 00214 } 00215 00216 int flags = fcntl(file.handle(), F_GETFL, 0); 00217 if (flags == -1) flags = 0; 00218 fcntl(file.handle(), F_SETFL, flags | O_NONBLOCK); 00219 } 00220 00221 if (_error) 00222 { 00223 RunEpilog(); 00224 return; 00225 } 00226 00227 SetRunning(true, false, false); 00228 00229 int buffer_size = (64 * 1024); // read about 64KB 00230 buffer_size /= TSPacket::kSize; 00231 buffer_size *= TSPacket::kSize; 00232 char *buffer = new char[buffer_size]; 00233 00234 LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): begin"); 00235 00236 _read_timer.start(); 00237 00238 int remainder = 0; 00239 while (_running_desired && !_error) 00240 { 00241 int bytes_read; 00242 if (_using_rtp) 00243 bytes_read = rtp.Read(buffer, buffer_size); 00244 else 00245 bytes_read = file.read(buffer, buffer_size); 00246 00247 if (bytes_read <= 0) 00248 { 00249 if (_read_timer.elapsed() >= 5000) 00250 { 00251 LOG(VB_RECORD, LOG_WARNING, LOC + 00252 "No data received for 5 seconds...checking tuning"); 00253 if (!VerifyTuning()) 00254 RepeatTuning(); 00255 _read_timer.start(); 00256 } 00257 00258 usleep(5000); 00259 continue; 00260 } 00261 00262 _read_timer.start(); 00263 00264 _listener_lock.lock(); 00265 00266 if (_stream_data_list.empty()) 00267 { 00268 _listener_lock.unlock(); 00269 continue; 00270 } 00271 00272 StreamDataList::const_iterator sit = _stream_data_list.begin(); 00273 for (; sit != _stream_data_list.end(); ++sit) 00274 remainder = sit.key()->ProcessData( 00275 reinterpret_cast<unsigned char*>(buffer), bytes_read); 00276 00277 _listener_lock.unlock(); 00278 if (remainder != 0) 00279 { 00280 LOG(VB_RECORD, LOG_INFO, LOC + 00281 QString("RunTS(): bytes_read = %1 remainder = %2") 00282 .arg(bytes_read).arg(remainder)); 00283 } 00284 } 00285 LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "shutdown"); 00286 00287 if (_using_rtp) 00288 rtp.StopStreaming(); 00289 else 00290 file.close(); 00291 00292 delete[] buffer; 00293 00294 LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "end"); 00295 00296 SetRunning(false, false, false); 00297 RunEpilog(); 00298 } 00299 00300 bool CetonStreamHandler::Open(void) 00301 { 00302 return Connect(); 00303 } 00304 00305 void CetonStreamHandler::Close(void) 00306 { 00307 if (_connected) 00308 { 00309 TunerOff(); 00310 _connected = false; 00311 } 00312 } 00313 00314 bool CetonStreamHandler::Connect(void) 00315 { 00316 if (!_valid) 00317 return false; 00318 00319 _connected = true; 00320 return true; 00321 } 00322 00323 bool CetonStreamHandler::EnterPowerSavingMode(void) 00324 { 00325 QMutexLocker locker(&_listener_lock); 00326 00327 if (!_stream_data_list.empty()) 00328 { 00329 LOG(VB_RECORD, LOG_INFO, LOC + 00330 "Ignoring request - video streaming active"); 00331 return false; 00332 } 00333 else 00334 { 00335 locker.unlock(); // _listener_lock 00336 TunerOff(); 00337 return true; 00338 } 00339 } 00340 00341 bool CetonStreamHandler::IsConnected(void) const 00342 { 00343 return _connected; 00344 } 00345 00346 bool CetonStreamHandler::VerifyTuning(void) 00347 { 00348 if (IsCableCardInstalled()) 00349 { 00350 uint prog = GetVar("mux", "ProgramNumber").toUInt(); 00351 if (prog == 0) 00352 { 00353 LOG(VB_RECORD, LOG_WARNING, LOC + 00354 "VerifyTuning detected program = 0"); 00355 return false; 00356 } 00357 } 00358 else 00359 { 00360 uint frequency = GetVar("tuner", "Frequency").toUInt(); 00361 if (frequency != _last_frequency) 00362 { 00363 LOG(VB_RECORD, LOG_WARNING, LOC + 00364 "VerifyTuning detected wrong frequency"); 00365 return false; 00366 } 00367 00368 QString modulation = GetVar("tuner", "Modulation"); 00369 if (modulation.toUpper() != _last_modulation.toUpper()) 00370 { 00371 LOG(VB_RECORD, LOG_WARNING, LOC + 00372 "VerifyTuning detected wrong modulation"); 00373 return false; 00374 } 00375 00376 uint program = GetVar("mux", "ProgramNumber").toUInt(); 00377 if (program != _last_program) 00378 { 00379 LOG(VB_RECORD, LOG_WARNING, LOC + 00380 "VerifyTuning detected wrong program"); 00381 return false; 00382 } 00383 } 00384 00385 QString carrier_lock = GetVar("tuner", "CarrierLock"); 00386 if (carrier_lock != "1") 00387 { 00388 LOG(VB_RECORD, LOG_WARNING, LOC + 00389 "VerifyTuning detected no carrier lock"); 00390 return false; 00391 } 00392 00393 QString pcr_lock = GetVar("tuner", "PCRLock"); 00394 if (pcr_lock != "1") 00395 { 00396 LOG(VB_RECORD, LOG_WARNING, LOC + 00397 "VerifyTuning detected no PCR lock"); 00398 return false; 00399 } 00400 00401 return true; 00402 } 00403 00404 void CetonStreamHandler::RepeatTuning(void) 00405 { 00406 if (IsCableCardInstalled()) 00407 { 00408 TuneVChannel(_last_vchannel); 00409 } 00410 else 00411 { 00412 TuneFrequency(_last_frequency, _last_modulation); 00413 TuneProgram(_last_program); 00414 } 00415 } 00416 00417 bool CetonStreamHandler::TunerOff(void) 00418 { 00419 bool result = TuneFrequency(0, "qam_256"); 00420 if (result && _using_cablecard) 00421 result = TuneVChannel("0"); 00422 00423 return result; 00424 } 00425 00426 bool CetonStreamHandler::TuneFrequency( 00427 uint frequency, const QString &modulation) 00428 { 00429 LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneFrequency(%1, %2)") 00430 .arg(frequency).arg(modulation)); 00431 00432 if (frequency >= 100000000) 00433 frequency /= 1000; 00434 00435 QString modulation_id = (modulation == "qam_256") ? "2" : 00436 (modulation == "qam_64") ? "0" : 00437 (modulation == "ntsc-m") ? "4" : 00438 (modulation == "8vsb") ? "6" : 00439 ""; 00440 if (modulation_id == "") 00441 return false; 00442 00443 _last_frequency = frequency; 00444 _last_modulation = modulation; 00445 00446 QUrl params; 00447 params.addQueryItem("instance_id", QString::number(_tuner)); 00448 params.addQueryItem("frequency", QString::number(frequency)); 00449 params.addQueryItem("modulation",modulation_id); 00450 params.addQueryItem("tuner","1"); 00451 params.addQueryItem("demod","1"); 00452 params.addQueryItem("rst_chnl","0"); 00453 params.addQueryItem("force_tune","0"); 00454 00455 QString response; 00456 uint status; 00457 bool result = HttpRequest( 00458 "POST", "/tune_request.cgi", params, response, status); 00459 00460 if (!result) 00461 { 00462 LOG(VB_GENERAL, LOG_ERR, LOC + 00463 QString("TuneFrequency() - HTTP status = %1 - response = %2") 00464 .arg(status).arg(response)); 00465 } 00466 00467 return result; 00468 } 00469 00470 bool CetonStreamHandler::TuneProgram(uint program) 00471 { 00472 LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneProgram(%1)").arg(program)); 00473 00474 QStringList program_list = GetProgramList(); 00475 if (!program_list.contains(QString::number(program))) 00476 { 00477 LOG(VB_GENERAL, LOG_ERR, LOC + 00478 QString("TuneProgram(%1) - Requested program not in the program list") 00479 .arg(program)); 00480 return false; 00481 }; 00482 00483 00484 _last_program = program; 00485 00486 QUrl params; 00487 params.addQueryItem("instance_id", QString::number(_tuner)); 00488 params.addQueryItem("program", QString::number(program)); 00489 00490 QString response; 00491 uint status; 00492 bool result = HttpRequest( 00493 "POST", "/program_request.cgi", params, response, status); 00494 00495 if (!result) 00496 { 00497 LOG(VB_GENERAL, LOG_ERR, LOC + 00498 QString("TuneProgram() - HTTP status = %1 - response = %2") 00499 .arg(status).arg(response)); 00500 } 00501 00502 return result; 00503 } 00504 00505 bool CetonStreamHandler::PerformTuneVChannel(const QString &vchannel) 00506 { 00507 LOG(VB_RECORD, LOG_INFO, LOC + QString("PerformTuneVChannel(%1)").arg(vchannel)); 00508 00509 QUrl params; 00510 params.addQueryItem("instance_id", QString::number(_tuner)); 00511 params.addQueryItem("channel", vchannel); 00512 00513 QString response; 00514 uint status; 00515 bool result = HttpRequest( 00516 "POST", "/channel_request.cgi", params, response, status); 00517 00518 if (!result) 00519 { 00520 LOG(VB_GENERAL, LOG_ERR, LOC + 00521 QString("PerformTuneVChannel() - HTTP status = %1 - response = %2") 00522 .arg(status).arg(response)); 00523 } 00524 00525 return result; 00526 } 00527 00528 00529 bool CetonStreamHandler::TuneVChannel(const QString &vchannel) 00530 { 00531 if ((vchannel != "0") && (_last_vchannel != "0")) 00532 ClearProgramNumber(); 00533 00534 LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneVChannel(%1)").arg(vchannel)); 00535 00536 _last_vchannel = vchannel; 00537 00538 return PerformTuneVChannel(vchannel); 00539 } 00540 00541 void CetonStreamHandler::ClearProgramNumber(void) 00542 { 00543 LOG(VB_RECORD, LOG_INFO, LOC + QString("ClearProgramNumber()")); 00544 PerformTuneVChannel("0"); 00545 for(int i=0; i<50; i++) 00546 { 00547 if (GetVar("mux", "ProgramNumber") == "0") 00548 return; 00549 usleep(20000); 00550 }; 00551 00552 LOG(VB_GENERAL, LOG_ERR, LOC + "Program number failed to clear"); 00553 } 00554 00555 uint CetonStreamHandler::GetProgramNumber(void) const 00556 { 00557 for(int i = 1; i <= 30; i++) 00558 { 00559 QString prog = GetVar("mux", "ProgramNumber"); 00560 LOG(VB_RECORD, LOG_INFO, LOC + 00561 QString("GetProgramNumber() got %1 on attempt %2") 00562 .arg(prog).arg(i)); 00563 00564 uint prognum = prog.toUInt(); 00565 if (prognum != 0) 00566 return prognum; 00567 00568 usleep(100000); 00569 }; 00570 00571 LOG(VB_GENERAL, LOG_ERR, LOC + 00572 "GetProgramNumber() failed to get a non-zero program number"); 00573 00574 return 0; 00575 } 00576 00577 QString CetonStreamHandler::GetVar( 00578 const QString §ion, const QString &variable) const 00579 { 00580 QString loc = LOC + QString("DoGetVar(%1,%2,%3,%4) - ") 00581 .arg(_ip_address).arg(_tuner).arg(section,variable); 00582 00583 QUrl params; 00584 params.addQueryItem("i", QString::number(_tuner)); 00585 params.addQueryItem("s", section); 00586 params.addQueryItem("v", variable); 00587 00588 QString response; 00589 uint status; 00590 if (!HttpRequest("GET", "/get_var.json", params, response, status)) 00591 { 00592 LOG(VB_GENERAL, LOG_ERR, loc + 00593 QString("HttpRequest failed - %1").arg(response)); 00594 return QString(); 00595 } 00596 00597 QRegExp regex("^\\{ \"?result\"?: \"(.*)\" \\}$"); 00598 if (regex.indexIn(response) == -1) 00599 { 00600 LOG(VB_GENERAL, LOG_ERR, loc + 00601 QString("unexpected http response: -->%1<--").arg(response)); 00602 return QString(); 00603 } 00604 00605 QString result = regex.cap(1); 00606 LOG(VB_RECORD, LOG_DEBUG, loc + QString("got: -->%1<--").arg(result)); 00607 return result; 00608 } 00609 00610 QStringList CetonStreamHandler::GetProgramList() 00611 { 00612 QString loc = LOC + QString("CetonHTTP: DoGetProgramList(%1,%2) - ") 00613 .arg(_ip_address).arg(_tuner); 00614 00615 QUrl params; 00616 params.addQueryItem("i", QString::number(_tuner)); 00617 00618 QString response; 00619 uint status; 00620 if (!HttpRequest("GET", "/get_pat.json", params, response, status)) 00621 { 00622 LOG(VB_GENERAL, LOG_ERR, 00623 loc + QString("HttpRequest failed - %1").arg(response)); 00624 return QStringList(); 00625 } 00626 00627 QRegExp regex( 00628 "^\\{ \"?length\"?: \\d+(, \"?results\"?: \\[ (.*) \\])? \\}$"); 00629 00630 if (regex.indexIn(response) == -1) 00631 { 00632 LOG(VB_GENERAL, LOG_ERR, 00633 loc + QString("returned unexpected output: -->%1<--") 00634 .arg(response)); 00635 return QStringList(); 00636 } 00637 00638 LOG(VB_RECORD, LOG_DEBUG, loc + QString("got: -->%1<--").arg(regex.cap(2))); 00639 return regex.cap(2).split(", "); 00640 } 00641 00642 bool CetonStreamHandler::HttpRequest( 00643 const QString &method, const QString &script, const QUrl ¶ms, 00644 QString &response, uint &status_code) const 00645 { 00646 QHttp http; 00647 http.setHost(_ip_address); 00648 00649 QByteArray request_params(params.encodedQuery()); 00650 00651 if (method == "GET") 00652 { 00653 QString path = script + "?" + QString(request_params); 00654 QHttpRequestHeader header(method, path); 00655 header.setValue("Host", _ip_address); 00656 http.request(header); 00657 } 00658 else 00659 { 00660 QHttpRequestHeader header(method, script); 00661 header.setValue("Host", _ip_address); 00662 header.setContentType("application/x-www-form-urlencoded"); 00663 http.request(header, request_params); 00664 } 00665 00666 while (http.hasPendingRequests() || http.currentId()) 00667 { 00668 usleep(5000); 00669 qApp->processEvents(); 00670 } 00671 00672 if (http.error() != QHttp::NoError) 00673 { 00674 status_code = 0; 00675 response = http.errorString(); 00676 return false; 00677 } 00678 00679 QHttpResponseHeader resp_header = http.lastResponse(); 00680 if (!resp_header.isValid()) 00681 { 00682 status_code = 0; 00683 response = "Completed but response object was not valid"; 00684 return false; 00685 } 00686 00687 status_code = resp_header.statusCode(); 00688 response = QString(http.readAll()); 00689 return true; 00690 }
1.7.6.1