MythTV  0.26-pre
cetonstreamhandler.cpp
Go to the documentation of this file.
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 &section, 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 &params,
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends