diff --git a/src/dvb/dvbliveview.cpp b/src/dvb/dvbliveview.cpp index 3e92fa6..e0a6ccd 100644 --- a/src/dvb/dvbliveview.cpp +++ b/src/dvb/dvbliveview.cpp @@ -1,734 +1,734 @@ /* * dvbliveview.cpp * * Copyright (C) 2007-2011 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include #include #include #include #include #include // bsd compatibility #include // bsd compatibility #include #include "dvbdevice.h" #include "dvbliveview.h" #include "dvbliveview_p.h" #include "dvbmanager.h" void DvbOsd::init(OsdLevel level_, const QString &channelName_, const QList &epgEntries) { level = level_; channelName = channelName_; if (epgEntries.size() < 1) { DvbEpgEntry empty; firstEntry = empty; secondEntry = empty; return; } firstEntry = *epgEntries.at(0); if (epgEntries.size() < 2) { DvbEpgEntry empty; secondEntry = empty; return; } secondEntry = *epgEntries.at(1); } QPixmap DvbOsd::paintOsd(QRect &rect, const QFont &font, Qt::LayoutDirection) { QFont osdFont = font; osdFont.setPointSize(20); QString timeString = QLocale().toString(QTime::currentTime()); QString entryString; int elapsedTime = 0; int totalTime = 0; if (firstEntry.channel.isValid()) { entryString = QLocale().toString(firstEntry.begin.toLocalTime().time()) + QLatin1Char(' ') + firstEntry.title; elapsedTime = firstEntry.begin.secsTo(QDateTime::currentDateTime()); totalTime = QTime(0, 0, 0).secsTo(firstEntry.duration); } if ((level == ShortOsd) && secondEntry.channel.isValid()) { entryString = entryString + QLatin1Char('\n') + QLocale().toString(secondEntry.begin.toLocalTime().time()) + QLatin1Char(' ') + secondEntry.title; } int lineHeight = QFontMetrics(osdFont).height(); QRect headerRect(5, 0, rect.width() - 10, lineHeight); QRect entryRect; if (level == ShortOsd) { entryRect = QRect(5, lineHeight + 9, rect.width() - 10, 2 * lineHeight); rect.setHeight(entryRect.bottom() + 1); } else { entryRect = QRect(5, lineHeight + 9, rect.width() - 10, lineHeight); } QPixmap pixmap(rect.size()); { QPainter painter(&pixmap); painter.fillRect(rect, Qt::black); painter.setFont(osdFont); painter.setPen(Qt::white); painter.drawText(headerRect, Qt::AlignLeft, channelName); painter.drawText(headerRect, Qt::AlignRight, timeString); painter.fillRect(5, lineHeight + 2, rect.width() - 10, 5, Qt::gray); painter.fillRect(6, lineHeight + 3, rect.width() - 12, 3, Qt::black); if ((elapsedTime > 0) && (elapsedTime <= totalTime)) { int width = (((rect.width() - 12) * elapsedTime + (totalTime / 2)) / totalTime); painter.fillRect(6, lineHeight + 3, width, 3, Qt::green); } painter.drawText(entryRect, Qt::AlignLeft, entryString); if (level == LongOsd) { QRect boundingRect = entryRect; if (!firstEntry.subheading.isEmpty()) { painter.drawText(entryRect.x(), boundingRect.bottom() + 1, entryRect.width(), lineHeight, Qt::AlignLeft, firstEntry.subheading, &boundingRect); } if (!firstEntry.details.isEmpty() && firstEntry.details != firstEntry.title) { painter.drawText(entryRect.x(), boundingRect.bottom() + 1, entryRect.width(), rect.height() - boundingRect.bottom() - 1, Qt::AlignLeft | Qt::TextWordWrap, firstEntry.details, &boundingRect); } if (boundingRect.bottom() < rect.bottom()) { rect.setBottom(boundingRect.bottom()); } } } return pixmap; } DvbLiveView::DvbLiveView(DvbManager *manager_, QObject *parent) : QObject(parent), manager(manager_), device(NULL), videoPid(-1), audioPid(-1), subtitlePid(-1) { mediaWidget = manager->getMediaWidget(); osdWidget = mediaWidget->getOsdWidget(); internal = new DvbLiveViewInternal(this); internal->mediaWidget = mediaWidget; connect(&internal->pmtFilter, SIGNAL(pmtSectionChanged(QByteArray)), this, SLOT(pmtSectionChanged(QByteArray))); connect(&patPmtTimer, SIGNAL(timeout()), this, SLOT(insertPatPmt())); connect(&osdTimer, SIGNAL(timeout()), this, SLOT(osdTimeout())); connect(internal, SIGNAL(currentAudioStreamChanged(int)), this, SLOT(currentAudioStreamChanged(int))); connect(internal, SIGNAL(currentSubtitleChanged(int)), this, SLOT(currentSubtitleChanged(int))); connect(internal, SIGNAL(replay()), this, SLOT(replay())); connect(internal, SIGNAL(playbackFinished()), this, SLOT(playbackFinished())); connect(internal, SIGNAL(playbackStatusChanged(MediaWidget::PlaybackStatus)), this, SLOT(playbackStatusChanged(MediaWidget::PlaybackStatus))); connect(internal, SIGNAL(previous()), this, SIGNAL(previous())); connect(internal, SIGNAL(next()), this, SIGNAL(next())); } DvbLiveView::~DvbLiveView() { } void DvbLiveView::replay() { if (device == NULL) { device = manager->requestDevice(channel->source, channel->transponder, DvbManager::Shared); } if (device == NULL) { channel = DvbSharedChannel(); mediaWidget->stop(); if (manager->getRecordingModel()->hasActiveRecordings()) { KMessageBox::sorry(manager->getParentWidget(), i18nc("@info", "All devices are used for recordings.")); } else { KMessageBox::information(manager->getParentWidget(), i18nc("@info", "No device found.")); } return; } internal->channelName = channel->name; internal->resetPipe(); mediaWidget->play(internal); internal->pmtFilter.setProgramNumber(channel->serviceId); startDevice(); internal->patGenerator.initPat(channel->transportStreamId, channel->serviceId, channel->pmtPid); videoPid = -1; audioPid = channel->audioPid; subtitlePid = -1; pmtSectionChanged(channel->pmtSectionData); patPmtTimer.start(500); internal->buffer.reserve(87 * 188); QTimer::singleShot(2000, this, SLOT(showOsd())); } void DvbLiveView::playbackFinished() { mediaWidget->play(internal); } const DvbSharedChannel &DvbLiveView::getChannel() const { return channel; } DvbDevice *DvbLiveView::getDevice() const { return device; } void DvbLiveView::playChannel(const DvbSharedChannel &channel_) { DvbDevice *newDevice = NULL; if ((channel.constData() != NULL) && (channel->source == channel_->source) && (channel->transponder.corresponds(channel_->transponder))) { newDevice = manager->requestDevice(channel->source, channel->transponder, DvbManager::Shared); } playbackStatusChanged(MediaWidget::Idle); channel = channel_; device = newDevice; replay(); } void DvbLiveView::toggleOsd() { if (channel.constData() == NULL) { return; } switch (internal->dvbOsd.level) { case DvbOsd::Off: internal->dvbOsd.init(DvbOsd::ShortOsd, QString(QLatin1String("%1 - %2")).arg(channel->number).arg(channel->name), manager->getEpgModel()->getCurrentNext(channel)); osdWidget->showObject(&internal->dvbOsd, 2500); osdTimer.start(2500); break; case DvbOsd::ShortOsd: internal->dvbOsd.level = DvbOsd::LongOsd; osdWidget->showObject(&internal->dvbOsd, -1); osdTimer.stop(); break; case DvbOsd::LongOsd: internal->dvbOsd.level = DvbOsd::Off; osdWidget->hideObject(); osdTimer.stop(); break; } } void DvbLiveView::pmtSectionChanged(const QByteArray &pmtSectionData) { internal->pmtSectionData = pmtSectionData; DvbPmtSection pmtSection(internal->pmtSectionData); DvbPmtParser pmtParser(pmtSection); videoPid = pmtParser.videoPid; for (int i = 0;; ++i) { if (i == pmtParser.audioPids.size()) { if (i > 0) { audioPid = pmtParser.audioPids.at(0).first; } else { audioPid = -1; } break; } if (pmtParser.audioPids.at(i).first == audioPid) { break; } } for (int i = 0;; ++i) { if (i == pmtParser.subtitlePids.size()) { subtitlePid = -1; break; } if (pmtParser.subtitlePids.at(i).first == subtitlePid) { break; } } updatePids(true); if (channel->isScrambled) { device->startDescrambling(internal->pmtSectionData, this); } if (internal->timeShiftFile.isOpen()) { return; } internal->audioStreams.clear(); audioPids.clear(); for (int i = 0; i < pmtParser.audioPids.size(); ++i) { const QPair &it = pmtParser.audioPids.at(i); if (!it.second.isEmpty()) { internal->audioStreams.append(it.second); } else { internal->audioStreams.append(QString::number(it.first)); } audioPids.append(it.first); } internal->currentAudioStream = audioPids.indexOf(audioPid); mediaWidget->audioStreamsChanged(); subtitlePids.clear(); for (int i = 0; i < pmtParser.subtitlePids.size(); ++i) { const QPair &it = pmtParser.subtitlePids.at(i); subtitlePids.append(it.first); } internal->currentSubtitle = subtitlePids.indexOf(subtitlePid); mediaWidget->subtitlesChanged(); } void DvbLiveView::insertPatPmt() { internal->buffer.append(internal->patGenerator.generatePackets()); internal->buffer.append(internal->pmtGenerator.generatePackets()); } void DvbLiveView::deviceStateChanged() { switch (device->getDeviceState()) { case DvbDevice::DeviceReleased: stopDevice(); device = manager->requestDevice(channel->source, channel->transponder, DvbManager::Shared); if (device != NULL) { startDevice(); } else { mediaWidget->stop(); osdWidget->showText(i18nc("message box", "No available device found."), 2500); } break; case DvbDevice::DeviceIdle: case DvbDevice::DeviceRotorMoving: case DvbDevice::DeviceTuning: case DvbDevice::DeviceTuned: break; } } void DvbLiveView::currentAudioStreamChanged(int currentAudioStream) { audioPid = -1; if ((currentAudioStream >= 0) && (currentAudioStream < audioPids.size())) { audioPid = audioPids.at(currentAudioStream); } updatePids(); } void DvbLiveView::currentSubtitleChanged(int currentSubtitle) { subtitlePid = -1; if ((currentSubtitle >= 0) && (currentSubtitle < subtitlePids.size())) { subtitlePid = subtitlePids.at(currentSubtitle); } updatePids(); } void DvbLiveView::playbackStatusChanged(MediaWidget::PlaybackStatus playbackStatus) { switch (playbackStatus) { case MediaWidget::Idle: if (device != NULL) { stopDevice(); manager->releaseDevice(device, DvbManager::Shared); device = NULL; } pids.clear(); patPmtTimer.stop(); osdTimer.stop(); internal->pmtSectionData.clear(); internal->patGenerator = DvbSectionGenerator(); internal->pmtGenerator = DvbSectionGenerator(); internal->buffer.clear(); internal->timeShiftFile.close(); internal->updateUrl(); internal->dvbOsd.init(DvbOsd::Off, QString(), QList()); osdWidget->hideObject(); break; case MediaWidget::Playing: if (internal->timeShiftFile.isOpen()) { // FIXME mediaWidget->play(internal); } break; case MediaWidget::Paused: if (internal->timeShiftFile.isOpen()) { break; } internal->timeShiftFile.setFileName(manager->getTimeShiftFolder() + QLatin1String("/TimeShift-") + QDateTime::currentDateTime().toString(QLatin1String("yyyyMMddThhmmss")) + QLatin1String(".m2t")); if (internal->timeShiftFile.exists() || !internal->timeShiftFile.open(QIODevice::WriteOnly)) { qCWarning(logDvb, "Cannot open file %s", qPrintable(internal->timeShiftFile.fileName())); internal->timeShiftFile.setFileName(QDir::homePath() + QLatin1String("/TimeShift-") + QDateTime::currentDateTime().toString(QLatin1String("yyyyMMddThhmmss")) + QLatin1String(".m2t")); if (internal->timeShiftFile.exists() || !internal->timeShiftFile.open(QIODevice::WriteOnly)) { qCWarning(logDvb, "Cannot open file %s", qPrintable(internal->timeShiftFile.fileName())); mediaWidget->stop(); break; } } updatePids(); // Use either the timeshift or the standard file URL internal->updateUrl(); // don't allow changes after starting time shift internal->audioStreams.clear(); internal->currentAudioStream = -1; mediaWidget->audioStreamsChanged(); internal->currentSubtitle = -1; mediaWidget->subtitlesChanged(); break; } } void DvbLiveView::showOsd() { if (internal->dvbOsd.level == DvbOsd::Off) { toggleOsd(); } } void DvbLiveView::osdTimeout() { internal->dvbOsd.level = DvbOsd::Off; osdTimer.stop(); } void DvbLiveView::startDevice() { foreach (int pid, pids) { device->addPidFilter(pid, internal); } device->addSectionFilter(channel->pmtPid, &internal->pmtFilter); connect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); if (channel->isScrambled && !internal->pmtSectionData.isEmpty()) { device->startDescrambling(internal->pmtSectionData, this); } manager->getEpgModel()->startEventFilter(device, channel); } void DvbLiveView::stopDevice() { manager->getEpgModel()->stopEventFilter(device, channel); if (channel->isScrambled && !internal->pmtSectionData.isEmpty()) { device->stopDescrambling(internal->pmtSectionData, this); } foreach (int pid, pids) { device->removePidFilter(pid, internal); } device->removeSectionFilter(channel->pmtPid, &internal->pmtFilter); disconnect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); } void DvbLiveView::updatePids(bool forcePatPmtUpdate) { DvbPmtSection pmtSection(internal->pmtSectionData); DvbPmtParser pmtParser(pmtSection); QSet newPids; - int pcr_pid = pmtSection.pcr_pid(); + int pcrPid = pmtSection.pcrPid(); bool updatePatPmt = forcePatPmtUpdate; bool isTimeShifting = internal->timeShiftFile.isOpen(); if (videoPid != -1) { newPids.insert(videoPid); } if (!isTimeShifting) { if (audioPid != -1) { newPids.insert(audioPid); } } else { for (int i = 0; i < pmtParser.audioPids.size(); ++i) { newPids.insert(pmtParser.audioPids.at(i).first); } } for (int i = 0; i < pmtParser.subtitlePids.size(); ++i) { newPids.insert(pmtParser.subtitlePids.at(i).first); } if (pmtParser.teletextPid != -1) { newPids.insert(pmtParser.teletextPid); } /* check PCR PID is set */ - if (pcr_pid != 0x1fff) { + if (pcrPid != 0x1fff) { /* Check not already in list */ - if (!newPids.contains(pcr_pid)) - newPids.insert(pcr_pid); + if (!newPids.contains(pcrPid)) + newPids.insert(pcrPid); } for (int i = 0; i < pids.size(); ++i) { int pid = pids.at(i); if (!newPids.remove(pid)) { device->removePidFilter(pid, internal); pids.removeAt(i); updatePatPmt = true; --i; } } foreach (int pid, newPids) { device->addPidFilter(pid, internal); pids.append(pid); updatePatPmt = true; } if (updatePatPmt) { internal->pmtGenerator.initPmt(channel->pmtPid, pmtSection, pids); insertPatPmt(); } } DvbLiveViewInternal::DvbLiveViewInternal(QObject *parent) : QObject(parent), mediaWidget(NULL), readFd(-1), writeFd(-1), retryCounter(0) { fileName = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1String("/dvbpipe.m2t"); QFile::remove(fileName); updateUrl(); if (mkfifo(QFile::encodeName(fileName).constData(), 0600) != 0) { qCWarning(logDvb, "Failed to open a fifo. Error: %d", errno); return; } readFd = open(QFile::encodeName(fileName).constData(), O_RDONLY | O_NONBLOCK); if (readFd < 0) { qCWarning(logDvb, "Failed to open fifo for read. Error: %d", errno); return; } writeFd = open(QFile::encodeName(fileName).constData(), O_WRONLY | O_NONBLOCK); if (writeFd < 0) { qCWarning(logDvb, "Failed to open fifo for write. Error: %d", errno); return; } notifier = new QSocketNotifier(writeFd, QSocketNotifier::Write, this); notifier->setEnabled(false); connect(notifier, SIGNAL(activated(int)), this, SLOT(writeToPipe())); emptyBuffer = true; } DvbLiveViewInternal::~DvbLiveViewInternal() { if (writeFd >= 0) { close(writeFd); } if (readFd >= 0) { close(readFd); } } void DvbLiveViewInternal::resetPipe() { retryCounter = 0; notifier->setEnabled(false); if (!buffers.isEmpty()) { buffer = buffers.at(0); buffers.clear(); } if (readFd >= 0) { if (buffer.isEmpty()) { buffer.resize(87 * 188); } while (read(readFd, buffer.data(), buffer.size()) > 0) { } } emptyBuffer = true; buffer.clear(); } void DvbLiveViewInternal::writeToPipe() { if (buffers.isEmpty()) { // disable notifier if the buffers are empty notifier->setEnabled(false); return; } do { const QByteArray ¤tBuffer = buffers.at(0); int bytesWritten = int(write(writeFd, currentBuffer.constData(), currentBuffer.size())); if (bytesWritten < 0) { // Some interrupt happened while writing. Retry. if (errno == EINTR) continue; if (++retryCounter > 50) { // Too much failures. Reset the pipe and return. qCWarning(logDvb, "libVLC is too slow! Let's reset the pipe"); resetPipe(); return; } // EAGAIN may happen when the pipe is full. // That's a normal condition. No need to report. if (errno != EAGAIN) qCWarning(logDvb, "Error %d while writing to pipe", errno); } else { retryCounter = 0; if (bytesWritten == currentBuffer.size()) { buffers.removeFirst(); continue; } if (bytesWritten > 0) buffers.first().remove(0, bytesWritten); } // If bytesWritten is less than buffer size, or returns an // error, there's no sense on wasting CPU time inside a loop break; } while (!buffers.isEmpty()); if (!buffers.isEmpty()) { // Wait for a notification that writeFd is ready to write notifier->setEnabled(true); } } void DvbLiveViewInternal::validateCurrentTotalTime(int ¤tTime, int &totalTime) const { if (emptyBuffer) return; totalTime = startTime.msecsTo(QTime::currentTime()); // Adjust it, if needed if (currentTime > totalTime) currentTime = totalTime -1; } void DvbLiveViewInternal::processData(const char data[188]) { buffer.append(data, 188); if (buffer.size() < (87 * 188)) { return; } if (!timeShiftFile.isOpen()) { if (writeFd >= 0) { buffers.append(buffer); writeToPipe(); if (emptyBuffer) { startTime = QTime::currentTime(); emptyBuffer = false; } } } else { timeShiftFile.write(buffer); // FIXME avoid buffer reallocation if (emptyBuffer) { startTime = QTime::currentTime(); emptyBuffer = false; } } buffer.clear(); buffer.reserve(87 * 188); } diff --git a/src/dvb/dvbrecording.cpp b/src/dvb/dvbrecording.cpp index 12a57dc..d5ba5fd 100644 --- a/src/dvb/dvbrecording.cpp +++ b/src/dvb/dvbrecording.cpp @@ -1,1062 +1,1062 @@ /* * dvbrecording.cpp * * Copyright (C) 2009-2011 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include #include #include #include #include #include "../ensurenopendingoperation.h" #include "dvbdevice.h" #include "dvbepg.h" #include "dvbliveview.h" #include "dvbmanager.h" #include "dvbrecording.h" #include "dvbrecording_p.h" #include "dvbtab.h" bool DvbRecording::validate() { if (!name.isEmpty() && channel.isValid() && begin.isValid() && (begin.timeSpec() == Qt::UTC) && duration.isValid()) { // the seconds and milliseconds aren't visible --> set them to zero begin = begin.addMSecs(-(QTime(0, 0, 0).msecsTo(begin.time()) % 60000)); end = begin.addSecs(QTime(0, 0, 0).secsTo(duration)); beginEPG = beginEPG.addMSecs(-(QTime(0, 0, 0).msecsTo(beginEPG.time()) % 60000)); endEPG = beginEPG.addSecs(QTime(0, 0, 0).secsTo(durationEPG)); repeat &= ((1 << 7) - 1); return true; } return false; } DvbRecordingModel::DvbRecordingModel(DvbManager *manager_, QObject *parent) : QObject(parent), manager(manager_), hasPendingOperation(false) { sqlInit(QLatin1String("RecordingSchedule"), QStringList() << QLatin1String("Name") << QLatin1String("Channel") << QLatin1String("Begin") << QLatin1String("Duration") << QLatin1String("Repeat") << QLatin1String("Subheading") << QLatin1String("Details") << QLatin1String("beginEPG") << QLatin1String("endEPG") << QLatin1String("durationEPG") << QLatin1String("Priority") << QLatin1String("Disabled")); // we regularly recheck the status of the recordings // this way we can keep retrying if the device was busy / tuning failed startTimer(5000); // compatibility code QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/recordings.dvb")); if (!file.exists()) { return; } if (!file.open(QIODevice::ReadOnly)) { qCWarning(logDvb, "Cannot open file %s", qPrintable(file.fileName())); return; } DvbChannelModel *channelModel = manager->getChannelModel(); QDataStream stream(&file); stream.setVersion(QDataStream::Qt_4_4); int version; stream >> version; if (version != 0x4d848730) { // the older version didn't store a magic number ... file.seek(0); version = 0; } while (!stream.atEnd()) { DvbRecording recording; recording.disabled = false; QString channelName; stream >> channelName; recording.channel = channelModel->findChannelByName(channelName); stream >> recording.beginEPG; recording.begin = recording.beginEPG.toUTC(); recording.beginEPG = recording.beginEPG.toLocalTime(); stream >> recording.duration; recording.durationEPG = recording.duration; QDateTime end; stream >> end; if (version != 0) { stream >> recording.repeat; } stream >> recording.subheading; stream >> recording.details; if (stream.status() != QDataStream::Ok) { qCWarning(logDvb, "Invalid recordings in file %s", qPrintable(file.fileName())); break; } addRecording(recording); } if (!file.remove()) { qCWarning(logDvb, "Cannot remove file %s", qPrintable(file.fileName())); } } DvbRecordingModel::~DvbRecordingModel() { if (hasPendingOperation) { qCWarning(logDvb, "Illegal recursive call"); } sqlFlush(); } bool DvbRecordingModel::hasRecordings() const { return !recordings.isEmpty(); } bool DvbRecordingModel::hasActiveRecordings() const { return !recordingFiles.isEmpty(); } DvbSharedRecording DvbRecordingModel::findRecordingByKey(const SqlKey &sqlKey) const { return recordings.value(sqlKey); } DvbRecording DvbRecordingModel::getCurrentRecording() { return currentRecording; } void DvbRecordingModel::setCurrentRecording(DvbRecording _currentRecording) { currentRecording = _currentRecording; } QMap DvbRecordingModel::getRecordings() const { return recordings; } QList DvbRecordingModel::getUnwantedRecordings() const { return unwantedRecordings; } DvbSharedRecording DvbRecordingModel::addRecording(DvbRecording &recording, bool checkForRecursion) { if (checkForRecursion) { if (hasPendingOperation) { qCWarning(logDvb, "Illegal recursive call"); return DvbSharedRecording(); } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); } if (!recording.validate()) { qCWarning(logDvb, "Invalid recording"); return DvbSharedRecording(); } recording.setSqlKey(sqlFindFreeKey(recordings)); if (!updateStatus(recording)) { return DvbSharedRecording(); } DvbSharedRecording newRecording(new DvbRecording(recording)); recordings.insert(*newRecording, newRecording); sqlInsert(*newRecording); emit recordingAdded(newRecording); return newRecording; } void DvbRecordingModel::updateRecording(DvbSharedRecording recording, DvbRecording &modifiedRecording) { if (hasPendingOperation) { qCWarning(logDvb, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); if (!recording.isValid() || (recordings.value(*recording) != recording) || !modifiedRecording.validate()) { qCWarning(logDvb, "Invalid recording"); return; } modifiedRecording.setSqlKey(*recording); if (!updateStatus(modifiedRecording)) { recordings.remove(*recording); recordingFiles.remove(*recording); sqlRemove(*recording); emit recordingRemoved(recording); return; } emit recordingAboutToBeUpdated(recording); *const_cast(recording.constData()) = modifiedRecording; sqlUpdate(*recording); emit recordingUpdated(recording); } void DvbRecordingModel::removeRecording(DvbSharedRecording recording) { if (hasPendingOperation) { qCWarning(logDvb, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); if (!recording.isValid() || (recordings.value(*recording) != recording)) { qCWarning(logDvb, "Invalid recording"); return; } recordings.remove(*recording); recordingFiles.remove(*recording); sqlRemove(*recording); emit recordingRemoved(recording); executeActionAfterRecording(*recording); findNewRecordings(); removeDuplicates(); disableConflicts(); } void DvbRecordingModel::disableLessImportant(DvbSharedRecording &recording1, DvbSharedRecording &recording2) { if (recording1->priority < recording2->priority) { DvbRecording rec1 = *(recording1.constData()); rec1.disabled = true; qCWarning(logDvb, "Disabled %s because %s has more priority", qPrintable(recording1->name), qPrintable(recording2->name)); } if (recording2->priority < recording1->priority) { DvbRecording rec2 = *(recording1.constData()); rec2.disabled = true; qCWarning(logDvb, "Disabled %s because %s has more priority", qPrintable(recording2->name), qPrintable(recording1->name)); } } void DvbRecordingModel::addToUnwantedRecordings(DvbSharedRecording recording) { unwantedRecordings.append(recording); qDebug("executed %s", qPrintable(recording->name)); } void DvbRecordingModel::executeActionAfterRecording(DvbRecording recording) { QString stopCommand = manager->getActionAfterRecording(); stopCommand.replace("%filename", recording.filename); if (!stopCommand.isEmpty()) { QProcess* child = new QProcess(); child->start(stopCommand); qCWarning(logDvb, "Not executing command after recording"); } qDebug("executed."); } void DvbRecordingModel::removeDuplicates() { QList recordingList = QList(); DvbEpgModel *epgModel = manager->getEpgModel(); if (!epgModel) return; QMap recordingMap = epgModel->getRecordings(); foreach(DvbSharedRecording key, recordings.values()) { recordingList.append(key); } int i = 0; foreach(DvbSharedRecording rec1, recordingList) { int j = 0; DvbRecording loopEntry1 = *rec1; foreach(DvbSharedRecording rec2, recordingList) { DvbRecording loopEntry2 = *rec2; if (i < j) { if (loopEntry1.begin == loopEntry2.begin && loopEntry1.duration == loopEntry2.duration && loopEntry1.channel->name == loopEntry2.channel->name && loopEntry1.name == loopEntry2.name) { recordings.remove(recordings.key(rec1)); recordingMap.remove(rec1); qDebug("Removed. %s", qPrintable(loopEntry1.name)); } } j = j + 1; } i = i + 1; } epgModel->setRecordings(recordingMap); qDebug("executed."); } bool DvbRecordingModel::existsSimilarRecording(DvbEpgEntry recording) { bool found = false; DvbEpgEntry entry = recording; DvbEpgModel *epgModel = manager->getEpgModel(); if (!epgModel) return found; QMap recordingMap = epgModel->getRecordings(); foreach(DvbSharedRecording key, recordingMap.keys()) { DvbEpgEntry loopEntry = *(recordingMap.value(key)); int loopLength = 60 * 60 * loopEntry.duration.hour() + 60 * loopEntry.duration.minute() + loopEntry.duration.second(); int length = 60 * 60 * entry.duration.hour() + 60 * entry.duration.minute() + entry.duration.second(); QDateTime end = entry.begin.addSecs(length); QDateTime loopEnd = loopEntry.begin.addSecs(loopLength); if (QString::compare(entry.channel->name, loopEntry.channel->name) == 0) { // Is included in an existing recording if (entry.begin <= loopEntry.begin && end >= loopEnd) { found = true; break; // Includes an existing recording } else if (entry.begin >= loopEntry.begin && end <= loopEnd) { found = true; break; } } } QList unwantedRecordingsList = manager->getRecordingModel()->getUnwantedRecordings(); foreach(DvbSharedRecording unwanted, unwantedRecordingsList) { DvbRecording loopEntry = *unwanted; if (QString::compare(entry.begin.toString(), ((loopEntry.begin.addSecs(manager->getBeginMargin()))).toString()) == 0 && QString::compare(entry.channel->name, loopEntry.channel->name) == 0 && QString::compare((entry.duration).toString(), loopEntry.duration.addSecs(- manager->getBeginMargin() - manager->getEndMargin()).toString()) == 0) { qDebug("Found from unwanteds %s", qPrintable(loopEntry.name)); found = true; break; } } return found; } void DvbRecordingModel::disableConflicts() { int maxSize = 1; // manager->getDeviceConfigs().size(); // foreach(DvbDeviceConfig config, manager->getDeviceConfigs()) // { // maxSize = maxSize + config.numberOfTuners; // } QList recordingList = QList(); foreach(DvbSharedRecording rec, recordings.values()) { if (!(rec->disabled)) { recordingList.append(rec); } } foreach(DvbSharedRecording rec1, recordingList) { QList conflictList = QList(); conflictList.append(rec1); foreach(DvbSharedRecording rec2, recordingList) { if (isInConflictWithAll(rec2, conflictList)) { conflictList.append(rec2); qDebug("conflict: '%s' '%s' and '%s' '%s'", qPrintable(rec1->name), qPrintable(rec1->begin.toString()), qPrintable(rec2->name), qPrintable(rec2->begin.toString())); } } if (conflictList.size() > maxSize) { disableLeastImportants(conflictList); } } } bool DvbRecordingModel::areInConflict(DvbSharedRecording recording1, DvbSharedRecording recording2) { int length1 = 60 * 60 * recording1->duration.hour() + 60 * recording1->duration.minute() + recording1->duration.second(); QDateTime end1 = recording1->begin.addSecs(length1); int length2 = 60 * 60 * recording2->duration.hour() + 60 * recording2->duration.minute() + recording2->duration.second(); QDateTime end2 = recording2->begin.addSecs(length2); if (!recording1->disabled && !recording1->disabled) { if (recording1->channel->transportStreamId != recording2->channel->transportStreamId) { if (recording2->begin > recording1->begin && recording2->begin < end1) { return true; } if (recording1->begin > recording2->begin && recording1->begin < end2) { return true; } } } return false; } bool DvbRecordingModel::isInConflictWithAll(DvbSharedRecording rec, QList recList) { foreach(DvbSharedRecording listRec, recList) { if (!areInConflict(rec, listRec)) { return false; } } return true; } int DvbRecordingModel::getNumberOfLeastImportants(QList recList) { DvbSharedRecording leastImportantShared = getLeastImportant(recList); int leastImportance = leastImportantShared->priority; int count = 0; foreach(DvbSharedRecording listRecShared, recList) { if (listRecShared->priority == leastImportance) { count = count + 1; } } return count; } DvbSharedRecording DvbRecordingModel::getLeastImportant(QList recList) { DvbSharedRecording leastImportant = recList.value(0); foreach(DvbSharedRecording listRec, recList) { qDebug("name and priority %s %s", qPrintable(listRec->name), qPrintable(listRec->priority)); if (listRec->priority < leastImportant->priority) { leastImportant = listRec; } } qDebug("least important: %s", qPrintable(leastImportant->name)); return leastImportant; } void DvbRecordingModel::disableLeastImportants(QList recList) { int numberOfLeastImportants = getNumberOfLeastImportants(recList); if (numberOfLeastImportants < recList.size()) { DvbSharedRecording leastImportantShared = getLeastImportant(recList); int leastImportance = leastImportantShared->priority; foreach(DvbSharedRecording listRecShared, recList) { DvbRecording listRec = *(listRecShared.constData()); if (listRecShared->priority == leastImportance) { listRec.disabled = true; updateRecording(listRecShared, listRec); qDebug("disabled: %s %s", qPrintable(listRec.name), qPrintable(listRec.begin.toString())); } } } } void DvbRecordingModel::findNewRecordings() { DvbEpgModel *epgModel = manager->getEpgModel(); if (!epgModel) return; QMap epgMap = epgModel->getEntries(); foreach(DvbEpgEntryId key, epgMap.keys()) { DvbEpgEntry entry = *(epgMap.value(key)); QString title = entry.title; QStringList regexList = manager->getRecordingRegexList(); int i = 0; foreach(QString regex, regexList) { QRegExp recordingRegex = QRegExp(regex); if (!recordingRegex.isEmpty()) { if (recordingRegex.indexIn(title) != -1) { if (!DvbRecordingModel::existsSimilarRecording(*epgMap.value(key))) { int priority = manager->getRecordingRegexPriorityList().value(i); epgModel->scheduleProgram(epgMap.value(key), manager->getBeginMargin(), manager->getEndMargin(), false, priority); qDebug("scheduled %s", qPrintable(title)); } } } i = i + 1; } } qDebug("executed."); } void DvbRecordingModel::timerEvent(QTimerEvent *event) { Q_UNUSED(event) QDateTime currentDateTime = QDateTime::currentDateTime().toUTC(); foreach (const DvbSharedRecording &recording, recordings) { if (recording->end <= currentDateTime) { DvbRecording modifiedRecording = *recording; updateRecording(recording, modifiedRecording); } } foreach (const DvbSharedRecording &recording, recordings) { if ((recording->status != DvbRecording::Recording) && (recording->begin <= currentDateTime)) { DvbRecording modifiedRecording = *recording; updateRecording(recording, modifiedRecording); } } } void DvbRecordingModel::bindToSqlQuery(SqlKey sqlKey, QSqlQuery &query, int index) const { DvbSharedRecording recording = recordings.value(sqlKey); if (!recording.isValid()) { qCWarning(logDvb, "Invalid recording"); return; } query.bindValue(index++, recording->name); query.bindValue(index++, recording->channel->name); query.bindValue(index++, recording->begin.toString(Qt::ISODate) + QLatin1Char('Z')); query.bindValue(index++, recording->duration.toString(Qt::ISODate)); query.bindValue(index++, recording->repeat); query.bindValue(index++, recording->subheading); query.bindValue(index++, recording->details); query.bindValue(index++, recording->beginEPG.toString(Qt::ISODate)); query.bindValue(index++, recording->endEPG.toString(Qt::ISODate)); query.bindValue(index++, recording->durationEPG.toString(Qt::ISODate)); query.bindValue(index++, recording->priority); query.bindValue(index++, recording->disabled); } bool DvbRecordingModel::insertFromSqlQuery(SqlKey sqlKey, const QSqlQuery &query, int index) { DvbRecording *recording = new DvbRecording(); DvbSharedRecording newRecording(recording); recording->name = query.value(index++).toString(); recording->channel = manager->getChannelModel()->findChannelByName(query.value(index++).toString()); recording->begin = QDateTime::fromString(query.value(index++).toString(), Qt::ISODate).toUTC(); recording->duration = QTime::fromString(query.value(index++).toString(), Qt::ISODate); recording->repeat = query.value(index++).toInt(); recording->subheading = query.value(index++).toString(); recording->details = query.value(index++).toString(); recording->beginEPG = QDateTime::fromString(query.value(index++).toString(), Qt::ISODate).toLocalTime(); recording->endEPG = QDateTime::fromString(query.value(index++).toString(), Qt::ISODate).toLocalTime(); recording->durationEPG = QTime::fromString(query.value(index++).toString(), Qt::ISODate); recording->priority = query.value(index++).toInt(); recording->disabled = query.value(index++).toBool(); if (recording->validate()) { recording->setSqlKey(sqlKey); recordings.insert(*newRecording, newRecording); return true; } return false; } /* * Returns -1 if no upcoming recordings. */ int DvbRecordingModel::getSecondsUntilNextRecording() const { signed long timeUntil = -1; foreach (const DvbSharedRecording &recording, recordings) { DvbRecording rec = *recording; int length = 60 * 60 * rec.duration.hour() + 60 * rec.duration.minute() + rec.duration.second(); QDateTime end = rec.begin.addSecs(length); if (rec.disabled || end < QDateTime::currentDateTime().toUTC()) { continue; } if (end > QDateTime::currentDateTime().toUTC() && rec.begin <= QDateTime::currentDateTime().toUTC()) { timeUntil = 0; qDebug("Rec ongoing %s", qPrintable(rec.name)); break; } if (rec.begin > QDateTime::currentDateTime().toUTC()) { if (timeUntil == -1 || timeUntil > rec.begin.toTime_t() - QDateTime::currentDateTime().toUTC().toTime_t()) { timeUntil = rec.begin.toTime_t() - QDateTime::currentDateTime().toUTC().toTime_t(); } } } qDebug("returned TRUE %ld", timeUntil); return timeUntil; } bool DvbRecordingModel::isScanWhenIdle() const { return manager->isScanWhenIdle(); } bool DvbRecordingModel::shouldWeScanChannels() const { int numberOfChannels = manager->getChannelModel()->getChannels().size(); int idleTime = 1000 * 3600 + 1; // TODO if (idleTime > 1000 * 3600) { if (DvbRecordingModel::getSecondsUntilNextRecording() > numberOfChannels * 10) { if (DvbRecordingModel::isScanWhenIdle()) { qDebug("Scan on Idle enabled"); return true; } } } return false; } void delay(int seconds) { QTime dieTime= QTime::currentTime().addSecs(seconds); while (QTime::currentTime() < dieTime) QCoreApplication::processEvents(QEventLoop::AllEvents, 100); qCInfo(logDvb, "Delayed for %d seconds", seconds); } void DvbRecordingModel::scanChannels() { qDebug("auto-scan channels"); if (shouldWeScanChannels()) { DvbChannelModel *channelModel = manager->getChannelModel(); QMap channelMap = channelModel->getChannels(); foreach (int channelInt, channelMap.keys()) { DvbSharedChannel channel; if (channelInt > 0) { channel = channelModel->findChannelByNumber(channelInt); } if (channel.isValid()) { // TODO update tab qDebug("Executed %s", qPrintable(channel->name)); manager->getLiveView()->playChannel(channel); delay(5); } } } } bool DvbRecordingModel::updateStatus(DvbRecording &recording) { QDateTime currentDateTimeLocal = QDateTime::currentDateTime(); QDateTime currentDateTime = currentDateTimeLocal.toUTC(); if (recording.end <= currentDateTime) { if (recording.repeat == 0) { return false; } recordingFiles.remove(recording); // take care of DST switches QDateTime beginLocal = recording.begin.toLocalTime(); QDateTime endLocal = recording.end.toLocalTime(); int days = endLocal.daysTo(currentDateTimeLocal); if (endLocal.addDays(days) <= currentDateTimeLocal) { ++days; } // QDate::dayOfWeek() and our dayOfWeek differ by one int dayOfWeek = (beginLocal.date().dayOfWeek() - 1 + days); for (int j = 0; j < 7; ++j) { if ((recording.repeat & (1 << (dayOfWeek % 7))) != 0) { break; } ++days; ++dayOfWeek; } recording.begin = beginLocal.addDays(days).toUTC(); recording.end = recording.begin.addSecs(QTime().secsTo(recording.duration)); } if (recording.begin <= currentDateTime) { QExplicitlySharedDataPointer recordingFile = recordingFiles.value(recording); if (recordingFile.constData() == NULL) { recordingFile = new DvbRecordingFile(manager); recordingFiles.insert(recording, recordingFile); } if (recordingFile->start(recording)) { recording.status = DvbRecording::Recording; } else { recording.status = DvbRecording::Error; } } else { recording.status = DvbRecording::Inactive; recordingFiles.remove(recording); } return true; } DvbRecordingFile::DvbRecordingFile(DvbManager *manager_) : manager(manager_), device(NULL), pmtValid(false) { connect(&pmtFilter, SIGNAL(pmtSectionChanged(QByteArray)), this, SLOT(pmtSectionChanged(QByteArray))); connect(&patPmtTimer, SIGNAL(timeout()), this, SLOT(insertPatPmt())); } DvbRecordingFile::~DvbRecordingFile() { stop(); } bool DvbRecordingFile::start(DvbRecording &recording) { if (recording.disabled) { return false; } if (!file.isOpen()) { QString folder = manager->getRecordingFolder(); QDate currentDate = QDate::currentDate(); QTime currentTime = QTime::currentTime(); QString filename = manager->getNamingFormat(); filename = filename.replace("%year", currentDate.toString("yyyy")); filename = filename.replace("%month", currentDate.toString("MM")); filename = filename.replace("%day", currentDate.toString("dd")); filename = filename.replace("%hour", currentTime.toString("hh")); filename = filename.replace("%min", currentTime.toString("mm")); filename = filename.replace("%sec", currentTime.toString("ss")); filename = filename.replace("%channel", recording.channel->name); filename = filename.replace("%title", QString(recording.name)); filename = filename.replace(QLatin1Char('/'), QLatin1Char('_')); if (filename == "") { filename = QString(recording.name); } QString path = folder + QLatin1Char('/') + filename; for (int attempt = 0; attempt < 100; ++attempt) { if (attempt == 0) { file.setFileName(path + QLatin1String(".m2t")); recording.filename = filename + QLatin1String(".m2t"); } else { file.setFileName(path + QLatin1Char('-') + QString::number(attempt) + QLatin1String(".m2t")); recording.filename = filename + QLatin1Char('-') + QString::number(attempt) + QLatin1String(".m2t"); } if (file.exists()) { continue; } if (file.open(QIODevice::WriteOnly)) { break; } else { qCWarning(logDvb, "Cannot open file %s. Error: %d", qPrintable(file.fileName()), errno); } if ((attempt == 0) && !QDir(folder).exists()) { if (QDir().mkpath(folder)) { attempt = -1; continue; } else { qCWarning(logDvb, "Cannot create folder %s", qPrintable(folder)); } } if (folder != QDir::homePath()) { folder = QDir::homePath(); path = folder + QLatin1Char('/') + QString(recording.name).replace(QLatin1Char('/'), QLatin1Char('_')); attempt = -1; continue; } break; } if (manager->createInfoFile()) { QString infoFile = path + ".txt"; QFile file(infoFile); if (file.open(QIODevice::ReadWrite)) { QTextStream stream(&file); stream << "EPG info" << endl; stream << "Title: " + QString(recording.name) << endl; stream << "Description: " + QString(recording.subheading) << endl; //stream << "Details: " + QString(recording.details) << endl; // Details is almost always empty stream << "Channel: " + QString(recording.channel->name) << endl; stream << "Date: " + recording.beginEPG.toLocalTime().toString("yyyy-MM-dd") << endl; stream << "Start time: " + recording.beginEPG.toLocalTime().toString("hh:mm:ss") << endl; stream << "Duration: " + recording.durationEPG.toString("HH:mm:ss") << endl; } } if (!file.isOpen()) { qCWarning(logDvb, "Cannot open file %s", qPrintable(file.fileName())); return false; } } if (device == NULL) { channel = recording.channel; device = manager->requestDevice(channel->source, channel->transponder, DvbManager::Prioritized); if (device == NULL) { qCWarning(logDvb, "Cannot find a suitable device"); return false; } connect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); pmtFilter.setProgramNumber(channel->serviceId); device->addSectionFilter(channel->pmtPid, &pmtFilter); pmtSectionData = channel->pmtSectionData; patGenerator.initPat(channel->transportStreamId, channel->serviceId, channel->pmtPid); if (channel->isScrambled && !pmtSectionData.isEmpty()) { device->startDescrambling(pmtSectionData, this); } } manager->getRecordingModel()->setCurrentRecording(recording); return true; } void DvbRecordingFile::stop() { if (device != NULL) { if (channel->isScrambled && !pmtSectionData.isEmpty()) { device->stopDescrambling(pmtSectionData, this); } foreach (int pid, pids) { device->removePidFilter(pid, this); } device->removeSectionFilter(channel->pmtPid, &pmtFilter); disconnect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); manager->releaseDevice(device, DvbManager::Prioritized); device = NULL; } pmtValid = false; patPmtTimer.stop(); patGenerator.reset(); pmtGenerator.reset(); pmtSectionData.clear(); pids.clear(); buffers.clear(); file.close(); channel = DvbSharedChannel(); manager->getRecordingModel()->executeActionAfterRecording(manager->getRecordingModel()->getCurrentRecording()); manager->getRecordingModel()->findNewRecordings(); manager->getRecordingModel()->removeDuplicates(); manager->getRecordingModel()->disableConflicts(); } void DvbRecordingFile::deviceStateChanged() { if (device->getDeviceState() == DvbDevice::DeviceReleased) { foreach (int pid, pids) { device->removePidFilter(pid, this); } device->removeSectionFilter(channel->pmtPid, &pmtFilter); disconnect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); if (channel->isScrambled && !pmtSectionData.isEmpty()) { device->stopDescrambling(pmtSectionData, this); } device = manager->requestDevice(channel->source, channel->transponder, DvbManager::Prioritized); if (device != NULL) { connect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); device->addSectionFilter(channel->pmtPid, &pmtFilter); foreach (int pid, pids) { device->addPidFilter(pid, this); } if (channel->isScrambled && !pmtSectionData.isEmpty()) { device->startDescrambling(pmtSectionData, this); } } else { // TODO stop(); } } } void DvbRecordingFile::pmtSectionChanged(const QByteArray &pmtSectionData_) { pmtSectionData = pmtSectionData_; DvbPmtSection pmtSection(pmtSectionData); DvbPmtParser pmtParser(pmtSection); - int pcr_pid = pmtSection.pcr_pid(); + int pcrPid = pmtSection.pcrPid(); QSet newPids; if (pmtParser.videoPid != -1) { newPids.insert(pmtParser.videoPid); } for (int i = 0; i < pmtParser.audioPids.size(); ++i) { newPids.insert(pmtParser.audioPids.at(i).first); } for (int i = 0; i < pmtParser.subtitlePids.size(); ++i) { newPids.insert(pmtParser.subtitlePids.at(i).first); } if (pmtParser.teletextPid != -1) { newPids.insert(pmtParser.teletextPid); } /* check PCR PID is set */ - if (pcr_pid != 0x1fff) { + if (pcrPid != 0x1fff) { /* Check not already in list */ - if (!newPids.contains(pcr_pid)) - newPids.insert(pcr_pid); + if (!newPids.contains(pcrPid)) + newPids.insert(pcrPid); } for (int i = 0; i < pids.size(); ++i) { int pid = pids.at(i); if (!newPids.remove(pid)) { device->removePidFilter(pid, this); pids.removeAt(i); --i; } } foreach (int pid, newPids) { device->addPidFilter(pid, this); pids.append(pid); } pmtGenerator.initPmt(channel->pmtPid, pmtSection, pids); if (!pmtValid) { pmtValid = true; file.write(patGenerator.generatePackets()); file.write(pmtGenerator.generatePackets()); foreach (const QByteArray &buffer, buffers) { file.write(buffer); } buffers.clear(); patPmtTimer.start(500); } insertPatPmt(); if (channel->isScrambled) { device->startDescrambling(pmtSectionData, this); } } void DvbRecordingFile::insertPatPmt() { if (!pmtValid) { pmtSectionChanged(channel->pmtSectionData); return; } file.write(patGenerator.generatePackets()); file.write(pmtGenerator.generatePackets()); } void DvbRecordingFile::processData(const char data[188]) { if (!pmtValid) { if (!patPmtTimer.isActive()) { patPmtTimer.start(1000); QByteArray nextBuffer; nextBuffer.reserve(348 * 188); buffers.append(nextBuffer); } QByteArray &buffer = buffers.last(); buffer.append(data, 188); if (buffer.size() >= (348 * 188)) { QByteArray nextBuffer; nextBuffer.reserve(348 * 188); buffers.append(nextBuffer); } return; } file.write(data, 188); } diff --git a/src/dvb/dvbsi.h b/src/dvb/dvbsi.h index 9b4bbe0..75f9551 100644 --- a/src/dvb/dvbsi.h +++ b/src/dvb/dvbsi.h @@ -1,1361 +1,1361 @@ /* * dvbsi.h * * Copyright (C) 2008-2011 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef DVBSI_H #define DVBSI_H #include #include #include #include #include #include "dvbbackenddevice.h" class DvbPmtSection; class DvbSectionData { public: bool isValid() const { return (length > 0); } unsigned char at(int index) const { return data[index]; } const char *getData() const { return data; } int getLength() const { return length; } QByteArray toByteArray() const { return QByteArray(data, length); } protected: DvbSectionData() { } ~DvbSectionData() { } void initSectionData() { data = NULL; length = 0; size = 0; } void initSectionData(const char *data_, int length_, int size_) { data = data_; length = length_; size = size_; } int getSize() const { return size; } private: const char *data; int length; int size; }; class DvbSection : public DvbSectionData { public: int getSectionLength() const { return getLength(); } int tableId() const { return at(0); } bool isStandardSection() const { return (at(1) & 0x80) != 0; } protected: DvbSection() { } ~DvbSection() { } void initSection(const char *data, int size); private: Q_DISABLE_COPY(DvbSection) }; class DvbStandardSection : public DvbSection { public: int tableIdExtension() const { return (at(3) << 8 | at(4)); } int versionNumber() const { return (at(5) >> 1) & ((1 << 5) - 1); } bool currentNextIndicator() const { return (at(5) & 0x01) != 0; } int sectionNumber() const { return at(6); } int lastSectionNumber() const { return at(7); } static int verifyCrc32(const char *data, int size); static const unsigned int crc32Table[]; protected: DvbStandardSection() { } ~DvbStandardSection() { } void initStandardSection(const char *data, int size); private: Q_DISABLE_COPY(DvbStandardSection) }; class DvbSiText { public: static QString convertText(const char *data, int size); static void setOverride6937(bool override); private: enum TextEncoding { Iso6937 = 0, Iso8859_1 = 1, Iso8859_2 = 2, Iso8859_3 = 3, Iso8859_4 = 4, Iso8859_5 = 5, Iso8859_6 = 6, Iso8859_7 = 7, Iso8859_8 = 8, Iso8859_9 = 9, Iso8859_10 = 10, Iso8859_11 = 11, Iso8859_13 = 12, Iso8859_14 = 13, Iso8859_15 = 14, Iso2022_kr = 15, Iso10646_ucs2 = 16, Gb2312 = 17, Utf_16be = 18, Utf_8 = 19, EncodingTypeMax = 19 }; static QTextCodec *codecTable[EncodingTypeMax + 1]; static bool override6937; }; class DvbDescriptor : public DvbSectionData { public: DvbDescriptor(const char *data, int size) { initDescriptor(data, size); } ~DvbDescriptor() { } int descriptorTag() const { return at(0); } void advance() { initDescriptor(getData() + getLength(), getSize() - getLength()); } static int bcdToInt(unsigned int bcd, int multiplier) { int value = 0; while (bcd != 0) { value += (bcd & 0xf) * multiplier; multiplier *= 10; bcd >>= 4; } return value; } private: void initDescriptor(const char *data, int size); }; // ATSC "Multiple String Structure". See A/65C Section 6.10 class AtscPsipText { public: static QString convertText(const char *data, int size); private: static QString interpretTextData(const char *data, unsigned int len, unsigned int mode); }; // ATSC Huffman compressed string support, conforming to A/65C Annex C class AtscHuffmanString { public: static QString convertText(const char *data_, int size, int table); private: AtscHuffmanString(const char *data_, int size, int table); ~AtscHuffmanString(); bool hasBits(); unsigned char getBit(); unsigned char getByte(); void decompress(); const char *data; int bitsLeft; QString result; const unsigned short *offsets; const unsigned char *tableBase; static const unsigned short Huffman1Offsets[128]; static const unsigned char Huffman1Tables[]; static const unsigned short Huffman2Offsets[128]; static const unsigned char Huffman2Tables[]; }; class DvbPmtFilter : public QObject, public DvbSectionFilter { Q_OBJECT public: DvbPmtFilter() : programNumber(-1) { } ~DvbPmtFilter() { } void setProgramNumber(int programNumber_) { programNumber = programNumber_; } signals: void pmtSectionChanged(const QByteArray &pmtSectionData); private: void processSection(const char *data, int size); int programNumber; QByteArray lastPmtSectionData; }; class DvbSectionGenerator { public: DvbSectionGenerator() : versionNumber(0), continuityCounter(0) { } ~DvbSectionGenerator() { } void initPat(int transportStreamId, int programNumber, int pmtPid); void initPmt(int pmtPid, const DvbPmtSection §ion, const QList &pids); void reset() { packets.clear(); versionNumber = 0; continuityCounter = 0; } QByteArray generatePackets(); private: char *startSection(int sectionLength); void endSection(int sectionLength, int pid); QByteArray packets; int versionNumber; int continuityCounter; }; class DvbPmtParser { public: explicit DvbPmtParser(const DvbPmtSection §ion); ~DvbPmtParser() { } int videoPid; QList > audioPids; // QString = language code (may be empty) QList > subtitlePids; // QString = language code int teletextPid; }; class AtscEitSectionEntry : public DvbSectionData { public: AtscEitSectionEntry(const char *data, int size) { initEitSectionEntry(data, size); } ~AtscEitSectionEntry() { } void advance() { initEitSectionEntry(getData() + getLength(), getSize() - getLength()); } int eventId() const { return ((at(0) & 0x3f) << 8) | at(1); } int startTime() const { return (at(2) << 24) | (at(3) << 16) | (at(4) << 8) | at(5); } int duration() const { return ((at(6) & 0xf) << 16) | (at(7) << 8) | at(8); } QString title() const { return AtscPsipText::convertText(getData() + 10, titleLength); } private: void initEitSectionEntry(const char *data, int size); int titleLength; }; // everything below this line is automatically generated class DvbPatSectionEntry : public DvbSectionData { public: DvbPatSectionEntry(const char *data, int size) { initPatSectionEntry(data, size); } ~DvbPatSectionEntry() { } void advance() { initPatSectionEntry(getData() + getLength(), getSize() - getLength()); } int programNumber() const { return (at(0) << 8) | at(1); } int pid() const { return ((at(2) & 0x1f) << 8) | at(3); } private: void initPatSectionEntry(const char *data, int size); }; class DvbPmtSectionEntry : public DvbSectionData { public: DvbPmtSectionEntry(const char *data, int size) { initPmtSectionEntry(data, size); } ~DvbPmtSectionEntry() { } void advance() { initPmtSectionEntry(getData() + getLength(), getSize() - getLength()); } int streamType() const { return at(0); } int pid() const { return ((at(1) & 0x1f) << 8) | at(2); } DvbDescriptor descriptors() const { return DvbDescriptor(getData() + 5, getLength() - 5); } private: void initPmtSectionEntry(const char *data, int size); }; class DvbSdtSectionEntry : public DvbSectionData { public: DvbSdtSectionEntry(const char *data, int size) { initSdtSectionEntry(data, size); } ~DvbSdtSectionEntry() { } void advance() { initSdtSectionEntry(getData() + getLength(), getSize() - getLength()); } int serviceId() const { return (at(0) << 8) | at(1); } bool isScrambled() const { return ((at(3) & 0x10) != 0); } DvbDescriptor descriptors() const { return DvbDescriptor(getData() + 5, getLength() - 5); } private: void initSdtSectionEntry(const char *data, int size); }; class DvbEitSectionEntry : public DvbSectionData { public: DvbEitSectionEntry(const char *data, int size) { initEitSectionEntry(data, size); } ~DvbEitSectionEntry() { } void advance() { initEitSectionEntry(getData() + getLength(), getSize() - getLength()); } int startDate() const { return (at(2) << 8) | at(3); } int startTime() const { return (at(4) << 16) | (at(5) << 8) | at(6); } int duration() const { return (at(7) << 16) | (at(8) << 8) | at(9); } DvbDescriptor descriptors() const { return DvbDescriptor(getData() + 12, getLength() - 12); } private: void initEitSectionEntry(const char *data, int size); }; class DvbEitContentEntry : public DvbSectionData { public: DvbEitContentEntry(const char *data, int size) { initEitContentEntry(data, size); } ~DvbEitContentEntry() { } void advance() { initEitContentEntry(getData() + getLength(), getSize() - getLength()); } int contentNibbleLevel1() const { return (at(0) >> 4); } int contentNibbleLevel2() const { return (at(0) & 0xf); } int userByte() const { return at(1); } private: void initEitContentEntry(const char *data, int size); }; class DvbParentalRatingEntry : public DvbSectionData { public: DvbParentalRatingEntry(const char *data, int size) { initParentalRatingEntry(data, size); } ~DvbParentalRatingEntry() { } void advance() { initParentalRatingEntry(getData() + getLength(), getSize() - getLength()); } int languageCode1() const { return at(0); } int languageCode2() const { return at(1); } int languageCode3() const { return at(2); } int rating() const { return at(3); } private: void initParentalRatingEntry(const char *data, int size); }; class DvbNitSectionEntry : public DvbSectionData { public: DvbNitSectionEntry(const char *data, int size) { initNitSectionEntry(data, size); } ~DvbNitSectionEntry() { } void advance() { initNitSectionEntry(getData() + getLength(), getSize() - getLength()); } DvbDescriptor descriptors() const { return DvbDescriptor(getData() + 6, getLength() - 6); } private: void initNitSectionEntry(const char *data, int size); }; class AtscMgtSectionEntry : public DvbSectionData { public: AtscMgtSectionEntry(const char *data, int size) { initMgtSectionEntry(data, size); } ~AtscMgtSectionEntry() { } void advance() { initMgtSectionEntry(getData() + getLength(), getSize() - getLength()); } int tableType() const { return (at(0) << 8) | at(1); } int pid() const { return ((at(2) & 0x1f) << 8) | at(3); } DvbDescriptor descriptors() const { return DvbDescriptor(getData() + 11, getLength() - 11); } private: void initMgtSectionEntry(const char *data, int size); }; class AtscVctSectionEntry : public DvbSectionData { public: AtscVctSectionEntry(const char *data, int size) { initVctSectionEntry(data, size); } ~AtscVctSectionEntry() { } void advance() { initVctSectionEntry(getData() + getLength(), getSize() - getLength()); } int shortName1() const { return (at(0) << 8) | at(1); } int shortName2() const { return (at(2) << 8) | at(3); } int shortName3() const { return (at(4) << 8) | at(5); } int shortName4() const { return (at(6) << 8) | at(7); } int shortName5() const { return (at(8) << 8) | at(9); } int shortName6() const { return (at(10) << 8) | at(11); } int shortName7() const { return (at(12) << 8) | at(13); } int majorNumber() const { return ((at(14) & 0xf) << 6) | (at(15) >> 2); } int minorNumber() const { return ((at(15) & 0x3) << 8) | at(16); } int programNumber() const { return (at(24) << 8) | at(25); } bool isScrambled() const { return ((at(26) & 0x20) != 0); } int sourceId() const { return (at(28) << 8) | at(29); } DvbDescriptor descriptors() const { return DvbDescriptor(getData() + 32, getLength() - 32); } private: void initVctSectionEntry(const char *data, int size); }; class DvbLanguageDescriptor : public DvbDescriptor { public: explicit DvbLanguageDescriptor(const DvbDescriptor &descriptor); ~DvbLanguageDescriptor() { } int languageCode1() const { return at(2); } int languageCode2() const { return at(3); } int languageCode3() const { return at(4); } private: Q_DISABLE_COPY(DvbLanguageDescriptor) }; class DvbSubtitleDescriptor : public DvbDescriptor { public: explicit DvbSubtitleDescriptor(const DvbDescriptor &descriptor); ~DvbSubtitleDescriptor() { } int languageCode1() const { return at(2); } int languageCode2() const { return at(3); } int languageCode3() const { return at(4); } int subtitleType() const { return at(5); } private: Q_DISABLE_COPY(DvbSubtitleDescriptor) }; class DvbServiceDescriptor : public DvbDescriptor { public: explicit DvbServiceDescriptor(const DvbDescriptor &descriptor); ~DvbServiceDescriptor() { } QString providerName() const { return DvbSiText::convertText(getData() + 4, providerNameLength); } QString serviceName() const { return DvbSiText::convertText(getData() + 5 + providerNameLength, serviceNameLength); } private: Q_DISABLE_COPY(DvbServiceDescriptor) int providerNameLength; int serviceNameLength; }; class DvbShortEventDescriptor : public DvbDescriptor { public: explicit DvbShortEventDescriptor(const DvbDescriptor &descriptor); ~DvbShortEventDescriptor() { } QString eventName() const { return DvbSiText::convertText(getData() + 6, eventNameLength); } QString text() const { return DvbSiText::convertText(getData() + 7 + eventNameLength, textLength); } private: Q_DISABLE_COPY(DvbShortEventDescriptor) int eventNameLength; int textLength; }; class DvbExtendedEventDescriptor : public DvbDescriptor { public: explicit DvbExtendedEventDescriptor(const DvbDescriptor &descriptor); ~DvbExtendedEventDescriptor() { } QString text() const { return DvbSiText::convertText(getData() + 8 + itemsLength, textLength); } private: Q_DISABLE_COPY(DvbExtendedEventDescriptor) int itemsLength; int textLength; }; class DvbContentDescriptor : public DvbDescriptor { public: explicit DvbContentDescriptor(const DvbDescriptor &descriptor); ~DvbContentDescriptor() { } DvbEitContentEntry contents() const { return DvbEitContentEntry(getData() + 2, getLength() - 2); } private: Q_DISABLE_COPY(DvbContentDescriptor) }; class DvbParentalRatingDescriptor : public DvbDescriptor { public: explicit DvbParentalRatingDescriptor(const DvbDescriptor &descriptor); ~DvbParentalRatingDescriptor() { } DvbParentalRatingEntry contents() const { return DvbParentalRatingEntry(getData() + 2, getLength() - 2); } private: Q_DISABLE_COPY(DvbParentalRatingDescriptor) }; class DvbCableDescriptor : public DvbDescriptor { public: explicit DvbCableDescriptor(const DvbDescriptor &descriptor); ~DvbCableDescriptor() { } int frequency() const { return (at(2) << 24) | (at(3) << 16) | (at(4) << 8) | at(5); } int modulation() const { return at(8); } int symbolRate() const { return (at(9) << 20) | (at(10) << 12) | (at(11) << 4) | (at(12) >> 4); } int fecRate() const { return (at(12) & 0xf); } private: Q_DISABLE_COPY(DvbCableDescriptor) }; class DvbSatelliteDescriptor : public DvbDescriptor { public: explicit DvbSatelliteDescriptor(const DvbDescriptor &descriptor); ~DvbSatelliteDescriptor() { } int frequency() const { return (at(2) << 24) | (at(3) << 16) | (at(4) << 8) | at(5); } int polarization() const { return ((at(8) & 0x7f) >> 5); } int rollOff() const { return ((at(8) & 0x1f) >> 3); } bool isDvbS2() const { return ((at(8) & 0x4) != 0); } int modulation() const { return (at(8) & 0x3); } int symbolRate() const { return (at(9) << 20) | (at(10) << 12) | (at(11) << 4) | (at(12) >> 4); } int fecRate() const { return (at(12) & 0xf); } private: Q_DISABLE_COPY(DvbSatelliteDescriptor) }; class DvbTerrestrialDescriptor : public DvbDescriptor { public: explicit DvbTerrestrialDescriptor(const DvbDescriptor &descriptor); ~DvbTerrestrialDescriptor() { } int frequency() const { return (at(2) << 24) | (at(3) << 16) | (at(4) << 8) | at(5); } int bandwidth() const { return (at(6) >> 5); } int constellation() const { return (at(7) >> 6); } int hierarchy() const { return ((at(7) & 0x3f) >> 3); } int fecRateHigh() const { return (at(7) & 0x7); } int fecRateLow() const { return (at(8) >> 5); } int guardInterval() const { return ((at(8) & 0x1f) >> 3); } int transmissionMode() const { return ((at(8) & 0x7) >> 1); } private: Q_DISABLE_COPY(DvbTerrestrialDescriptor) }; class IsdbTerrestrialDescriptor : public DvbDescriptor { public: explicit IsdbTerrestrialDescriptor(const DvbDescriptor &descriptor); ~IsdbTerrestrialDescriptor() { } int areaCode() const { return (at(2) << 4) | (at(3) >> 4); } int guardInterval() const { return ((at(3) & 0xf) >> 2); } int transmissionMode() const { return (at(3) & 0x3); } int frequencyLength() const { return (getLength() - 4) / 2; } int frequency(int idx) const { int pos = (idx * 2) + 4; return (at(pos) << 8) | at(pos + 1); } private: Q_DISABLE_COPY(IsdbTerrestrialDescriptor) }; class AtscChannelNameDescriptor : public DvbDescriptor { public: explicit AtscChannelNameDescriptor(const DvbDescriptor &descriptor); ~AtscChannelNameDescriptor() { } QString name() const { return AtscPsipText::convertText(getData() + 2, getLength() - 2); } private: Q_DISABLE_COPY(AtscChannelNameDescriptor) }; class DvbPatSection : public DvbStandardSection { public: DvbPatSection(const char *data, int size) { initPatSection(data, size); } explicit DvbPatSection(const QByteArray &byteArray) { initPatSection(byteArray.constData(), byteArray.size()); } ~DvbPatSection() { } int transportStreamId() const { return (at(3) << 8) | at(4); } DvbPatSectionEntry entries() const { return DvbPatSectionEntry(getData() + 8, getLength() - 12); } private: Q_DISABLE_COPY(DvbPatSection) void initPatSection(const char *data, int size); }; class DvbPmtSection : public DvbStandardSection { public: DvbPmtSection(const char *data, int size) { initPmtSection(data, size); } explicit DvbPmtSection(const QByteArray &byteArray) { initPmtSection(byteArray.constData(), byteArray.size()); } ~DvbPmtSection() { } int programNumber() const { return (at(3) << 8) | at(4); } - int pcr_pid() const + int pcrPid() const { return ((at(8) & 0x1f) << 8) | at(9); } DvbDescriptor descriptors() const { return DvbDescriptor(getData() + 12, descriptorsLength); } DvbPmtSectionEntry entries() const { return DvbPmtSectionEntry(getData() + 12 + descriptorsLength, getLength() - (16 + descriptorsLength)); } private: Q_DISABLE_COPY(DvbPmtSection) void initPmtSection(const char *data, int size); int descriptorsLength; }; class DvbSdtSection : public DvbStandardSection { public: DvbSdtSection(const char *data, int size) { initSdtSection(data, size); } explicit DvbSdtSection(const QByteArray &byteArray) { initSdtSection(byteArray.constData(), byteArray.size()); } ~DvbSdtSection() { } int originalNetworkId() const { return (at(8) << 8) | at(9); } DvbSdtSectionEntry entries() const { return DvbSdtSectionEntry(getData() + 11, getLength() - 15); } private: Q_DISABLE_COPY(DvbSdtSection) void initSdtSection(const char *data, int size); }; class DvbEitSection : public DvbStandardSection { public: DvbEitSection(const char *data, int size) { initEitSection(data, size); } explicit DvbEitSection(const QByteArray &byteArray) { initEitSection(byteArray.constData(), byteArray.size()); } ~DvbEitSection() { } int serviceId() const { return (at(3) << 8) | at(4); } int transportStreamId() const { return (at(8) << 8) | at(9); } int originalNetworkId() const { return (at(10) << 8) | at(11); } DvbEitSectionEntry entries() const { return DvbEitSectionEntry(getData() + 14, getLength() - 18); } private: Q_DISABLE_COPY(DvbEitSection) void initEitSection(const char *data, int size); }; class DvbNitSection : public DvbStandardSection { public: DvbNitSection(const char *data, int size) { initNitSection(data, size); } explicit DvbNitSection(const QByteArray &byteArray) { initNitSection(byteArray.constData(), byteArray.size()); } ~DvbNitSection() { } DvbDescriptor descriptors() const { return DvbDescriptor(getData() + 10, descriptorsLength); } DvbNitSectionEntry entries() const { return DvbNitSectionEntry(getData() + 12 + descriptorsLength, entriesLength); } private: Q_DISABLE_COPY(DvbNitSection) void initNitSection(const char *data, int size); int descriptorsLength; int entriesLength; }; class AtscMgtSection : public DvbStandardSection { public: AtscMgtSection(const char *data, int size) { initMgtSection(data, size); } explicit AtscMgtSection(const QByteArray &byteArray) { initMgtSection(byteArray.constData(), byteArray.size()); } ~AtscMgtSection() { } int entryCount() const { return (at(9) << 8) | at(10); } AtscMgtSectionEntry entries() const { return AtscMgtSectionEntry(getData() + 11, getLength() - 17); } private: Q_DISABLE_COPY(AtscMgtSection) void initMgtSection(const char *data, int size); }; class AtscVctSection : public DvbStandardSection { public: AtscVctSection(const char *data, int size) { initVctSection(data, size); } explicit AtscVctSection(const QByteArray &byteArray) { initVctSection(byteArray.constData(), byteArray.size()); } ~AtscVctSection() { } int entryCount() const { return at(9); } AtscVctSectionEntry entries() const { return AtscVctSectionEntry(getData() + 10, getLength() - 14); } private: Q_DISABLE_COPY(AtscVctSection) void initVctSection(const char *data, int size); }; class AtscEitSection : public DvbStandardSection { public: AtscEitSection(const char *data, int size) { initEitSection(data, size); } explicit AtscEitSection(const QByteArray &byteArray) { initEitSection(byteArray.constData(), byteArray.size()); } ~AtscEitSection() { } int sourceId() const { return (at(3) << 8) | at(4); } int entryCount() const { return at(9); } AtscEitSectionEntry entries() const { return AtscEitSectionEntry(getData() + 10, getLength() - 14); } private: Q_DISABLE_COPY(AtscEitSection) void initEitSection(const char *data, int size); }; class AtscEttSection : public DvbStandardSection { public: AtscEttSection(const char *data, int size) { initEttSection(data, size); } explicit AtscEttSection(const QByteArray &byteArray) { initEttSection(byteArray.constData(), byteArray.size()); } ~AtscEttSection() { } int sourceId() const { return (at(9) << 8) | at(10); } int eventId() const { return (at(11) << 6) | (at(12) >> 2); } int messageType() const { return (at(12) & 0x3); } QString text() const { return AtscPsipText::convertText(getData() + 13, getLength() - 17); } private: Q_DISABLE_COPY(AtscEttSection) void initEttSection(const char *data, int size); }; #endif /* DVBSI_H */