MythTV master
dvbcam.cpp
Go to the documentation of this file.
1/*
2 * Class DVBCam
3 *
4 * Original Project
5 * MythTV http://www.mythtv.org
6 *
7 * Author(s):
8 * Jesper Sorensen
9 * - Changed to work with Taylor Jacob's DVB rewrite
10 * Kenneth Aafloy
11 * - General Implementation
12 *
13 * Description:
14 * This Class has been developed from bits n' pieces of other
15 * projects.
16 *
17 *
18 *
19 * This program is free software; you can redistribute it and/or
20 * modify it under the terms of the GNU General Public License
21 * as published by the Free Software Foundation; either version 2
22 * of the License, or (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
32 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
33 *
34 */
35
36// C++
37#include <cstdio>
38#include <cstdlib>
39
40// C
41#include <fcntl.h>
42#include <sys/ioctl.h>
43#include <sys/poll.h>
44#include <linux/dvb/ca.h>
45
46// Qt
47#include <QString>
48#include <QList>
49#include <QMap>
50
51// MythTV
52#include "libmythbase/mthread.h"
54
55#include "cardutil.h"
56#include "dvbcam.h"
57#include "dvbchannel.h"
58#include "dvbdev/dvbci.h"
59#include "dvbrecorder.h"
60#include "recorderbase.h"
61
62#define LOC QString("DVBCam(%1): ").arg(m_device)
63
64DVBCam::DVBCam(QString device)
65 : m_device(std::move(device))
66{
68 QByteArray dev = dvbdev.toLatin1();
69 int cafd = open(dev.constData(), O_RDWR);
70 if (cafd >= 0)
71 {
72 ca_caps_t caps;
73 // slot_num will be uninitialised if ioctl fails
74 if (ioctl(cafd, CA_GET_CAP, &caps) >= 0)
75 m_numslots = caps.slot_num;
76 else
77 LOG(VB_GENERAL, LOG_ERR, LOC + "ioctl CA_GET_CAP failed: " + ENO);
78
79 close(cafd);
80 }
81}
82
84{
85 Stop();
86}
87
88bool DVBCam::Start(void)
89{
90 if (m_numslots == 0)
91 return false;
92
93 m_havePmt = false;
94 m_pmtSent = false;
95 m_pmtUpdated = false;
96 m_pmtAdded = false;
97
99 QByteArray dev = dvbdev.toLatin1();
100 m_ciHandler = cCiHandler::CreateCiHandler(dev.constData());
101 if (!m_ciHandler)
102 {
103 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to initialize CI handler");
104 return false;
105 }
106
107 QMutexLocker locker(&m_ciHandlerLock);
108 m_ciHandlerDoRun = true;
109 m_ciHandlerThread = new MThread("DVBCam", this);
112 m_ciHandlerWait.wait(locker.mutex(), 1000);
113
115 LOG(VB_DVBCAM, LOG_INFO, LOC + "CI handler successfully initialized!");
116
117 return m_ciHandlerRunning;
118}
119
120bool DVBCam::Stop(void)
121{
122 {
123 QMutexLocker locker(&m_ciHandlerLock);
125 {
126 m_ciHandlerDoRun = false;
127 locker.unlock();
129 locker.relock();
130 delete m_ciHandlerThread;
131 m_ciHandlerThread = nullptr;
132 }
133
134 if (m_ciHandler)
135 {
136 delete m_ciHandler;
137 m_ciHandler = nullptr;
138 }
139 }
140
141 QMutexLocker locker(&m_pmtLock);
142 pmt_list_t::iterator it;
143
144 for (it = m_pmtList.begin(); it != m_pmtList.end(); ++it)
145 delete *it;
146 m_pmtList.clear();
147
148 for (it = m_pmtAddList.begin(); it != m_pmtAddList.end(); ++it)
149 delete *it;
150 m_pmtAddList.clear();
151
152 return true;
153}
154
156{
158 if (enq != nullptr)
159 {
160 if (enq->Text() != nullptr)
161 LOG(VB_DVBCAM, LOG_INFO, LOC + QString("CAM: Received message: %1")
162 .arg(enq->Text()));
163 delete enq;
164 }
165
167 if (menu != nullptr)
168 {
169 if (menu->TitleText() != nullptr)
170 LOG(VB_DVBCAM, LOG_INFO, LOC + QString("CAM: Menu Title: %1")
171 .arg(menu->TitleText()));
172 if (menu->SubTitleText() != nullptr)
173 LOG(VB_DVBCAM, LOG_INFO, LOC + QString("CAM: Menu SubTitle: %1")
174 .arg(menu->SubTitleText()));
175 if (menu->BottomText() != nullptr)
176 LOG(VB_DVBCAM, LOG_INFO, LOC + QString("CAM: Menu BottomText: %1")
177 .arg(menu->BottomText()));
178
179 for (int i=0; i<menu->NumEntries(); i++)
180 {
181 if (menu->Entry(i) != nullptr)
182 LOG(VB_DVBCAM, LOG_INFO, LOC + QString("CAM: Menu Entry: %1")
183 .arg(menu->Entry(i)));
184 }
185
186 if (menu->Selectable())
187 {
188 LOG(VB_CHANNEL, LOG_INFO, LOC + "CAM: Menu is selectable");
189 }
190
191 if (menu->NumEntries() > 0)
192 {
193 LOG(VB_DVBCAM, LOG_INFO, LOC + "CAM: Selecting first entry");
194 menu->Select(0);
195 }
196 else
197 {
198 LOG(VB_DVBCAM, LOG_INFO, LOC + "CAM: Cancelling menu");
199 }
200
201 delete menu;
202 }
203}
204
205// Remove duplicate service IDs because the CAM needs to be setup only
206// once for each service even if the service is recorded twice.
207// This can happen with overlapping recordings on the same channel.
209{
210 QList<uint> unique_sids;
211 pmt_list_t::iterator it;
212
213 // Remove duplicates in m_pmtList
214 for (it = m_pmtList.begin(); it != m_pmtList.end(); )
215 {
216 const ChannelBase *chan = it.key();
217 uint inputId = chan->GetInputID();
218 const ProgramMapTable *pmt = (*it);
219 uint serviceId = pmt->ProgramNumber();
220 if (unique_sids.contains(serviceId))
221 {
222 it = m_pmtList.erase(it);
223 LOG(VB_DVBCAM, LOG_DEBUG, LOC + QString("Service [%1]%2 duplicate, removed from pmtList")
224 .arg(inputId).arg(serviceId));
225 }
226 else
227 {
228 unique_sids.append(serviceId);
229 ++it;
230 LOG(VB_DVBCAM, LOG_DEBUG, LOC + QString("Service [%1]%2 stays in pmtList")
231 .arg(inputId).arg(serviceId));
232 }
233 }
234
235 // Remove duplicates in m_pmtAddList
236 for (it = m_pmtAddList.begin(); it != m_pmtAddList.end(); )
237 {
238 const ChannelBase *chan = it.key();
239 uint inputId = chan->GetInputID();
240 const ProgramMapTable *pmt = (*it);
241 uint serviceId = pmt->ProgramNumber();
242 if (unique_sids.contains(serviceId))
243 {
244 it = m_pmtAddList.erase(it);
245 LOG(VB_DVBCAM, LOG_DEBUG, LOC + QString("Service [%1]%2 duplicate, removed from pmtAddList")
246 .arg(inputId).arg(serviceId));
247 }
248 else
249 {
250 unique_sids.append(serviceId);
251 ++it;
252 LOG(VB_DVBCAM, LOG_DEBUG, LOC + QString("Service [%1]%2 stays in pmtAddList")
253 .arg(inputId).arg(serviceId));
254 }
255 }
256}
257
258
260{
261 LOG(VB_DVBCAM, LOG_INFO, LOC + "CiHandler needs CA_PMT");
262 QMutexLocker locker(&m_pmtLock);
263
265
267 {
268 // Send added PMT
269 while (!m_pmtAddList.empty())
270 {
271 pmt_list_t::iterator it = m_pmtAddList.begin();
272 const ChannelBase *chan = it.key();
273 ProgramMapTable *pmt = (*it);
274 m_pmtList[chan] = pmt;
275 m_pmtAddList.erase(it);
276 SendPMT(*pmt, CPLM_ADD);
277 }
278
279 m_pmtUpdated = false;
280 m_pmtAdded = false;
281 return;
282 }
283
284 // Grab any added PMT
285 while (!m_pmtAddList.empty())
286 {
287 pmt_list_t::iterator it = m_pmtAddList.begin();
288 const ChannelBase *chan = it.key();
289 ProgramMapTable *pmt = (*it);
290 m_pmtList[chan] = pmt;
291 m_pmtAddList.erase(it);
292 }
293
294 uint length = m_pmtList.size();
295 uint count = 0;
296
297 for (auto *pmt : std::as_const(m_pmtList))
298 {
299 uint cplm = (count == 0) ? CPLM_FIRST : CPLM_MORE;
300 cplm = (count + 1 == length) ? CPLM_LAST : cplm;
301 cplm = (length == 1) ? CPLM_ONLY : cplm;
302
303 SendPMT(*pmt, cplm);
304
305 count++;
306 }
307
308 m_pmtSent = true;
309 m_pmtUpdated = false;
310 m_pmtAdded = false;
311}
312
313void DVBCam::run(void)
314{
315 LOG(VB_DVBCAM, LOG_INFO, LOC + "CI handler thread running");
316
317 QMutexLocker locker(&m_ciHandlerLock);
318 m_ciHandlerRunning = true;
319
320 while (m_ciHandlerDoRun)
321 {
322 locker.unlock();
323 if (m_ciHandler->Process())
324 {
325 if (m_ciHandler->HasUserIO())
326 HandleUserIO();
327
328 bool handle_pmt = m_pmtSent && (m_pmtUpdated || m_pmtAdded);
329 handle_pmt |= m_havePmt && m_ciHandler->NeedCaPmt();
330
331 if (handle_pmt)
332 HandlePMT();
333 }
334 locker.relock();
335 m_ciHandlerWait.wait(locker.mutex(), 10);
336 }
337
338 m_ciHandlerRunning = false;
339 LOG(VB_DVBCAM, LOG_INFO, LOC + "CiHandler thread stopped");
340}
341
342void DVBCam::SetPMT(const ChannelBase *chan, const ProgramMapTable *pmt)
343{
344 QMutexLocker locker(&m_pmtLock);
345
346 pmt_list_t::iterator it = m_pmtList.find(chan);
347 pmt_list_t::iterator it2 = m_pmtAddList.find(chan);
348 if (!pmt && (it != m_pmtList.end()))
349 {
350 delete *it;
351 m_pmtList.erase(it);
352 m_pmtUpdated = true;
353 }
354 else if (!pmt && (it2 != m_pmtAddList.end()))
355 {
356 delete *it2;
357 m_pmtAddList.erase(it2);
358 m_pmtAdded = !m_pmtAddList.empty();
359 }
360 else if (pmt && (m_pmtList.empty() || (it != m_pmtList.end())))
361 {
362 if (it != m_pmtList.end())
363 delete *it;
364 m_pmtList[chan] = new ProgramMapTable(*pmt);
365 m_havePmt = true;
366 m_pmtUpdated = true;
367 }
368 else if (pmt && (it == m_pmtList.end()))
369 {
370 m_pmtAddList[chan] = new ProgramMapTable(*pmt);
371 m_pmtAdded = true;
372 }
373}
374
375void DVBCam::SetTimeOffset(double offset_in_seconds)
376{
377 QMutexLocker locker(&m_ciHandlerLock);
378 if (m_ciHandler)
379 m_ciHandler->SetTimeOffset(offset_in_seconds);
380}
381
382static std::array<const std::string,6> cplm_info
383{
384 "CPLM_MORE",
385 "CPLM_FIRST",
386 "CPLM_LAST",
387 "CPLM_ONLY",
388 "CPLM_ADD",
389 "CPLM_UPDATE"
390};
391
392cCiCaPmt CreateCAPMT(const ProgramMapTable& /*pmt*/, const dvbca_vector &/*casids*/, uint /*cplm*/);
393
394/*
395 * Send a CA_PMT object to the CAM (see EN50221, section 8.4.3.4)
396 */
398{
399 bool success = false;
400
401 for (uint s = 0; s < (uint)m_ciHandler->NumSlots(); s++)
402 {
404
405 if (casids.empty())
406 {
407 LOG(success ? VB_DVBCAM : VB_GENERAL, LOG_ERR,
408 LOC + "CAM supports no CA systems! " +
409 QString("(Slot #%1)").arg(s));
410 continue;
411 }
412
413 LOG(VB_DVBCAM, LOG_INFO, LOC +
414 QString("Creating CA_PMT, ServiceID = %1")
415 .arg(pmt.ProgramNumber()));
416
417 cCiCaPmt capmt = CreateCAPMT(pmt, casids, cplm);
418
419 LOG(VB_DVBCAM, LOG_INFO, LOC +
420 QString("Sending CA_PMT with %1 to CI slot #%2")
421 .arg(QString::fromStdString(cplm_info[cplm])).arg(s));
422
423 if (!m_ciHandler->SetCaPmt(capmt, s))
424 {
425 LOG(success ? VB_DVBCAM : VB_GENERAL, LOG_ERR,
426 LOC + "CA_PMT send failed!");
427 }
428 else
429 {
430 success = true;
431 }
432 }
433}
434
435static void process_desc(cCiCaPmt &capmt,
436 const dvbca_vector &casids,
437 const desc_list_t &desc)
438{
439 desc_list_t::const_iterator it;
440 for (it = desc.begin(); it != desc.end(); ++it)
441 {
443 for (auto id : casids)
444 {
445 if (!cad.IsValid() || cad.SystemID() != id)
446 continue;
447
448 LOG(VB_DVBCAM, LOG_INFO, QString("DVBCam: Adding CA descriptor: "
449 "CASID(0x%2), ECM PID(0x%3)")
450 .arg(cad.SystemID(),0,16).arg(cad.PID(),0,16));
451
452 capmt.AddCaDescriptor(cad.SystemID(), cad.PID(),
453 cad.DataSize(), cad.Data());
454 }
455 }
456
457}
458
460 const dvbca_vector &casids,
461 uint cplm)
462{
463 cCiCaPmt capmt(pmt.ProgramNumber(), cplm);
464
465 // Add CA descriptors for the service
467 pmt.ProgramInfo(), pmt.ProgramInfoLength(),
469
470 process_desc(capmt, casids, gdesc);
471
472 // Add elementary streams + CA descriptors
473 for (uint i = 0; i < pmt.StreamCount(); i++)
474 {
475 LOG(VB_DVBCAM, LOG_INFO,
476 QString("DVBCam: Adding elementary stream: %1, pid(0x%2)")
477 .arg(pmt.StreamDescription(i, "dvb"))
478 .arg(pmt.StreamPID(i),0,16));
479
480 capmt.AddElementaryStream(pmt.StreamType(i), pmt.StreamPID(i));
481
483 pmt.StreamInfo(i), pmt.StreamInfoLength(i),
485
486 process_desc(capmt, casids, desc);
487 }
488 return capmt;
489}
@ DVB_DEV_CA
Definition: cardutil.h:33
static QString GetDeviceName(dvb_dev_type_t type, const QString &device)
Definition: cardutil.cpp:2967
Abstract class providing a generic interface to tuning hardware.
Definition: channelbase.h:32
virtual int GetInputID(void) const
Definition: channelbase.h:67
const unsigned char * Data(void) const
void SetPMT(const ChannelBase *chan, const ProgramMapTable *pmt)
Definition: dvbcam.cpp:342
pmt_list_t m_pmtAddList
Definition: dvbcam.h:58
void RemoveDuplicateServices(void)
Definition: dvbcam.cpp:208
void SendPMT(const ProgramMapTable &pmt, uint cplm)
Definition: dvbcam.cpp:397
DVBCam(QString device)
Definition: dvbcam.cpp:64
void HandlePMT(void)
Definition: dvbcam.cpp:259
void HandleUserIO(void)
Definition: dvbcam.cpp:155
MThread * m_ciHandlerThread
Definition: dvbcam.h:54
~DVBCam() override
Definition: dvbcam.cpp:83
bool m_pmtUpdated
Definition: dvbcam.h:61
bool m_ciHandlerRunning
Definition: dvbcam.h:52
void run(void) override
Definition: dvbcam.cpp:313
cCiHandler * m_ciHandler
Definition: dvbcam.h:53
bool Stop(void)
Definition: dvbcam.cpp:120
QMutex m_pmtLock
Definition: dvbcam.h:56
bool m_ciHandlerDoRun
Definition: dvbcam.h:51
QString m_device
Definition: dvbcam.h:46
QMutex m_ciHandlerLock
Definition: dvbcam.h:49
void SetTimeOffset(double offset_in_seconds)
Definition: dvbcam.cpp:375
int m_numslots
Definition: dvbcam.h:47
bool m_havePmt
Definition: dvbcam.h:59
QWaitCondition m_ciHandlerWait
Definition: dvbcam.h:50
pmt_list_t m_pmtList
Definition: dvbcam.h:57
bool m_pmtSent
Definition: dvbcam.h:60
bool m_pmtAdded
Definition: dvbcam.h:62
bool Start(void)
Definition: dvbcam.cpp:88
bool IsValid(void) const
static desc_list_t ParseOnlyInclude(const unsigned char *data, uint len, int excluded_descid)
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
A PMT table maps a program described in the ProgramAssociationTable to various PID's which describe t...
Definition: mpegtables.h:676
uint StreamCount(void) const
Definition: mpegtables.h:733
uint StreamPID(uint i) const
Definition: mpegtables.h:724
uint StreamType(uint i) const
Definition: mpegtables.h:721
const unsigned char * ProgramInfo(void) const
Definition: mpegtables.h:718
const unsigned char * StreamInfo(uint i) const
Definition: mpegtables.h:730
uint ProgramNumber(void) const
Definition: mpegtables.h:712
uint ProgramInfoLength(void) const
Definition: mpegtables.h:715
uint StreamInfoLength(uint i) const
Definition: mpegtables.h:727
QString StreamDescription(uint i, const QString &sistandard) const
Returns a better (and more expensive) string representation of type at stream index i than StreamType...
void AddCaDescriptor(int ca_system_id, int ca_pid, int data_len, const uint8_t *data)
Definition: dvbci.cpp:1551
void AddElementaryStream(int type, int pid)
Definition: dvbci.cpp:1516
const char * Text(void)
Definition: dvbci.h:107
virtual bool Process(void)=0
virtual bool HasUserIO(void)=0
virtual int NumSlots(void)=0
virtual cCiMenu * GetMenu(void)=0
static cCiHandler * CreateCiHandler(const char *FileName)
Definition: dvbci.cpp:1608
virtual bool NeedCaPmt(void)=0
virtual bool SetCaPmt(cCiCaPmt &CaPmt, int Slot)=0
virtual cCiEnquiry * GetEnquiry(void)=0
virtual void SetTimeOffset(double)
Definition: dvbci.h:156
virtual dvbca_vector GetCaSystemIds(int Slot)=0
Definition: dvbci.h:72
#define close
Definition: compat.h:39
static void process_desc(cCiCaPmt &capmt, const dvbca_vector &casids, const desc_list_t &desc)
Definition: dvbcam.cpp:435
#define LOC
Definition: dvbcam.cpp:62
cCiCaPmt CreateCAPMT(const ProgramMapTable &, const dvbca_vector &, uint)
Definition: dvbcam.cpp:459
static std::array< const std::string, 6 > cplm_info
Definition: dvbcam.cpp:383
#define CPLM_ONLY
Definition: dvbci.h:119
#define CPLM_FIRST
Definition: dvbci.h:117
#define CPLM_MORE
Definition: dvbci.h:116
#define CPLM_ADD
Definition: dvbci.h:120
#define CPLM_LAST
Definition: dvbci.h:118
std::vector< uint16_t > dvbca_vector
Definition: dvbci.h:44
unsigned int uint
Definition: freesurround.h:24
std::vector< const unsigned char * > desc_list_t
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
static MythThemedMenu * menu
STL namespace.