00001
00008
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
00017 #include <QCoreApplication>
00018 #include <QBuffer>
00019 #include <QFile>
00020 #include <QHttp>
00021 #include <QUrl>
00022
00023
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);
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();
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 }