MythTV master
streamhandler.cpp
Go to the documentation of this file.
1// -*- Mode: c++ -*-
2
3// C++ headers
4#include <algorithm>
5#include <utility>
6
7// MythTV headers
8#include "streamhandler.h"
9
10#include <QtGlobal>
11#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
12#include <QtSystemDetection>
13#endif
14
17
18#ifndef O_LARGEFILE
19#define O_LARGEFILE 0
20#endif
21
22#define LOC QString("SH[%1]: ").arg(m_inputId)
23
25{
26 QMutexLocker locker(&m_addRmLock);
27
28 {
29 QMutexLocker locker2(&m_listenerLock);
30 if (!m_streamDataList.empty())
31 {
32 LOG(VB_GENERAL, LOG_ERR, LOC +
33 "dtor & _stream_data_list not empty");
34 }
35 }
36
37 // This should never be triggered.. just to be safe..
38 if (m_running)
39 Stop();
40}
41
43 bool allow_section_reader,
44 bool needs_buffering,
45 const QString& output_file)
46{
47 QMutexLocker locker(&m_addRmLock);
48
49 LOG(VB_RECORD, LOG_INFO, LOC + QString("AddListener(0x%1) -- begin")
50 .arg((uint64_t)data,0,16));
51 if (!data)
52 {
53 LOG(VB_GENERAL, LOG_ERR, LOC +
54 QString("AddListener(0x%1) -- null data")
55 .arg((uint64_t)data,0,16));
56 return;
57 }
58
59 m_listenerLock.lock();
60
61 LOG(VB_RECORD, LOG_INFO, LOC + QString("AddListener(0x%1) -- locked")
62 .arg((uint64_t)data,0,16));
63
64 if (m_streamDataList.empty())
65 {
66 QMutexLocker locker2(&m_startStopLock);
67 m_allowSectionReader = allow_section_reader;
68 m_needsBuffering = needs_buffering;
69 }
70 else
71 {
72 QMutexLocker locker2(&m_startStopLock);
73 m_allowSectionReader &= allow_section_reader;
74 m_needsBuffering |= needs_buffering;
75 }
76
77 m_streamDataList[data] = output_file;
78
79 m_listenerLock.unlock();
80
81 Start();
82
83 LOG(VB_RECORD, LOG_INFO, LOC + QString("AddListener(0x%1) -- end")
84 .arg((uint64_t)data,0,16));
85}
86
88{
89 QMutexLocker locker(&m_addRmLock);
90
91 LOG(VB_RECORD, LOG_INFO, LOC + QString("RemoveListener(0x%1) -- begin")
92 .arg((uint64_t)data,0,16));
93 if (!data)
94 {
95 LOG(VB_GENERAL, LOG_ERR, LOC +
96 QString("RemoveListener(0x%1) -- null data")
97 .arg((uint64_t)data,0,16));
98 return;
99 }
100
101 m_listenerLock.lock();
102
103 LOG(VB_RECORD, LOG_INFO, LOC + QString("RemoveListener(0x%1) -- locked")
104 .arg((uint64_t)data,0,16));
105
106 StreamDataList::iterator it = m_streamDataList.find(data);
107
108 if (it != m_streamDataList.end())
109 {
110 if (!(*it).isEmpty())
112 m_streamDataList.erase(it);
113 }
114
115 m_listenerLock.unlock();
116
117 if (m_streamDataList.empty())
118 Stop();
119
120 LOG(VB_RECORD, LOG_INFO, LOC + QString("RemoveListener(0x%1) -- end")
121 .arg((uint64_t)data,0,16));
122}
123
125{
126 QMutexLocker locker(&m_startStopLock);
127
128 if (m_running)
129 {
132 {
133 LOG(VB_RECORD, LOG_INFO, LOC + "Restarting StreamHandler");
134 SetRunningDesired(false);
135 m_restarting = true;
136 locker.unlock();
137 wait();
138 locker.relock();
139 m_restarting = false;
140 }
141 }
142
143 if (m_running)
144 return;
145
146 m_eitPids.clear();
147
148 m_bError = false;
149 SetRunningDesired(true);
151
152 while (!m_running && !m_bError && m_runningDesired)
154
155 if (m_bError)
156 {
157 LOG(VB_GENERAL, LOG_ERR, LOC + "Start failed");
158 SetRunningDesired(false);
159 }
160}
161
163{
164 LOG(VB_RECORD, LOG_DEBUG, LOC + "Stopping");
166 wait();
167 LOG(VB_RECORD, LOG_DEBUG, LOC + "Stopped");
168}
169
171{
172 // This used to use QMutexLocker, but that sometimes left the
173 // mutex locked on exit, so...
174 m_startStopLock.lock();
175 bool r = m_running || m_restarting;
176 m_startStopLock.unlock();
177 return r;
178}
179
180void StreamHandler::SetRunning(bool is_running,
181 bool is_using_buffering,
182 bool is_using_section_reader)
183{
184 QMutexLocker locker(&m_startStopLock);
185 m_running = is_running;
186 m_usingBuffering = is_using_buffering;
187 m_usingSectionReader = is_using_section_reader;
188 m_runningStateChanged.wakeAll();
189}
190
192{
193 m_runningDesired = desired;
194 if (!desired)
195 MThread::exit(0);
196}
197
199{
200#ifdef DEBUG_PID_FILTERS
201 LOG(VB_RECORD, LOG_DEBUG, LOC + QString("AddPIDFilter(0x%1)")
202 .arg(info->m_pid, 0, 16));
203#endif // DEBUG_PID_FILTERS
204
205 QMutexLocker writing_locker(&m_pidLock);
206 m_pidInfo[info->m_pid] = info;
207
208 m_filtersChanged = true;
209
211
212 return true;
213}
214
216{
217#ifdef DEBUG_PID_FILTERS
218 LOG(VB_RECORD, LOG_DEBUG, LOC +
219 QString("RemovePIDFilter(0x%1)").arg(pid, 0, 16));
220#endif // DEBUG_PID_FILTERS
221
222 QMutexLocker write_locker(&m_pidLock);
223
224 PIDInfoMap::iterator it = m_pidInfo.find(pid);
225 if (it == m_pidInfo.end())
226 return false;
227
228 PIDInfo *tmp = *it;
229 m_pidInfo.erase(it);
230
231 bool ok = true;
232 if (tmp->IsOpen())
233 {
234 ok = tmp->Close(m_device);
236
238 }
239
240 delete tmp;
241
242 m_filtersChanged = true;
243
244 return ok;
245}
246
248{
249 QMutexLocker write_locker(&m_pidLock);
250
251#ifdef DEBUG_PID_FILTERS
252 LOG(VB_RECORD, LOG_DEBUG, LOC + "RemoveAllPIDFilters()");
253#endif // DEBUG_PID_FILTERS
254
255 std::vector<int> del_pids;
256 for (auto it = m_pidInfo.begin(); it != m_pidInfo.end(); ++it)
257 del_pids.push_back(it.key());
258
259 bool ok = true;
260 for (int & pid : del_pids)
261 ok &= RemovePIDFilter(pid);
262
263 return UpdateFilters() && ok;
264}
265
267{
268 QMutexLocker read_locker(&m_listenerLock);
269
270 for (auto it1 = m_streamDataList.cbegin(); it1 != m_streamDataList.cend(); ++it1)
271 {
272 std::vector<uint> add_eit;
273 std::vector<uint> del_eit;
274
275 MPEGStreamData *sd = it1.key();
276 if (sd->HasEITPIDChanges(m_eitPids) &&
277 sd->GetEITPIDChanges(m_eitPids, add_eit, del_eit))
278 {
279 for (uint eit : del_eit)
280 {
281 uint_vec_t::iterator it2;
282 it2 = std::ranges::find(m_eitPids, eit);
283 if (it2 != m_eitPids.end())
284 m_eitPids.erase(it2);
285 sd->RemoveListeningPID(eit);
286 }
287
288 for (uint eit : add_eit)
289 {
290 m_eitPids.push_back(eit);
291 sd->AddListeningPID(eit);
292 }
293 }
294 }
295}
296
298{
300
301 pid_map_t pids;
302
303 {
304 QMutexLocker read_locker(&m_listenerLock);
305 for (auto it = m_streamDataList.cbegin(); it != m_streamDataList.cend(); ++it)
306 it.key()->GetPIDs(pids);
307 }
308
309 QMap<uint, PIDInfo*> add_pids;
310 std::vector<uint> del_pids;
311
312 {
313 QMutexLocker read_locker(&m_pidLock);
314
315 // PIDs that need to be added..
316 for (auto lit = pids.constBegin(); lit != pids.constEnd(); ++lit)
317 {
318 if ((*lit != 0U) && (!m_pidInfo.contains(lit.key())))
319 {
320 add_pids[lit.key()] = CreatePIDInfo(
321 lit.key(), StreamID::PrivSec, 0);
322 }
323 }
324
325 // PIDs that need to be removed..
326 for (auto fit = m_pidInfo.cbegin(); fit != m_pidInfo.cend(); ++fit)
327 {
328 bool in_pids = pids.contains(fit.key());
329 if (!in_pids)
330 del_pids.push_back(fit.key());
331 }
332 }
333
334 // Remove PIDs
335 bool ok = true;
336 for (uint & pid : del_pids)
337 ok &= RemovePIDFilter(pid);
338
339 // Add PIDs
340 for (auto & pid : add_pids)
341 ok &= AddPIDFilter(pid);
342
343 // Cycle filters if it's been a while
344 if (m_cycleTimer.isRunning() && (m_cycleTimer.elapsed() > 1s))
346
347 return ok;
348}
349
351{
352 QMutexLocker reading_locker(&m_listenerLock);
353
355
356 for (auto it = m_streamDataList.cbegin(); it != m_streamDataList.cend(); ++it)
357 tmp = std::max(tmp, it.key()->GetPIDPriority(pid));
358
359 return tmp;
360}
361
362void StreamHandler::WriteMPTS(const unsigned char * buffer, uint len)
363{
364 if (m_mptsTfw == nullptr)
365 return;
366 m_mptsTfw->Write(buffer, len);
367}
368
369bool StreamHandler::AddNamedOutputFile([[maybe_unused]] const QString &file)
370{
371#ifndef Q_OS_WINDOWS
372 QMutexLocker lk(&m_mptsLock);
373
374 m_mptsFiles.insert(file);
375 QString fn = QString("%1.raw").arg(file);
376
377 if (m_mptsFiles.size() == 1)
378 {
379 m_mptsBaseFile = fn;
381 O_WRONLY|O_TRUNC|O_CREAT|O_LARGEFILE,
382 0644);
383 if (!m_mptsTfw->Open())
384 {
385 delete m_mptsTfw;
386 m_mptsTfw = nullptr;
387 return false;
388 }
389 LOG(VB_RECORD, LOG_INFO, LOC +
390 QString("Opened '%1'").arg(m_mptsBaseFile));
391 }
392 else
393 {
394 if (link(m_mptsBaseFile.toLocal8Bit().constData(),
395 fn.toLocal8Bit().constData())
396 < 0)
397 {
398 LOG(VB_GENERAL, LOG_ERR, LOC +
399 QString("Failed to link '%1' to '%2'")
400 .arg(m_mptsBaseFile, fn) + ENO);
401 }
402 else
403 {
404 LOG(VB_RECORD, LOG_INFO, LOC +
405 QString("linked '%1' to '%2'")
406 .arg(m_mptsBaseFile, fn));
407 }
408 }
409#endif // !defined( Q_OS_WINDOWS )
410 return true;
411}
412
413void StreamHandler::RemoveNamedOutputFile([[maybe_unused]] const QString &file)
414{
415#ifndef Q_OS_WINDOWS
416 QMutexLocker lk(&m_mptsLock);
417
418 QSet<QString>::iterator it = m_mptsFiles.find(file);
419 if (it != m_mptsFiles.end())
420 {
421 m_mptsFiles.erase(it);
422 if (m_mptsFiles.isEmpty())
423 {
424 delete m_mptsTfw;
425 m_mptsTfw = nullptr;
426 }
427 }
428#endif // !defined( Q_OS_WINDOWS )
429}
Encapsulates data about MPEG stream and emits events for each table.
virtual bool GetEITPIDChanges(const uint_vec_t &, uint_vec_t &, uint_vec_t &) const
virtual void RemoveListeningPID(uint pid)
virtual bool HasEITPIDChanges(const uint_vec_t &) const
virtual void AddListeningPID(uint pid, PIDPriority priority=kPIDPriorityNormal)
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:267
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
Definition: mthread.cpp:262
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
bool isRunning(void) const
Returns true if start() or restart() has been called at least once since construction and since any c...
Definition: mythtimer.cpp:135
virtual bool Close(const QString &)
Definition: streamhandler.h:39
bool IsOpen(void) const
Definition: streamhandler.h:40
QRecursiveMutex m_pidLock
bool AddPIDFilter(PIDInfo *info)
virtual bool AddNamedOutputFile(const QString &filename)
Called with _listener_lock locked just after adding new output file.
StreamDataList m_streamDataList
bool RemovePIDFilter(uint pid)
QString m_mptsBaseFile
MythTimer m_cycleTimer
void WriteMPTS(const unsigned char *buffer, uint len)
Write out a copy of the raw MPTS.
virtual void RemoveNamedOutputFile(const QString &filename)
Called with _listener_lock locked just before removing old output file.
void Start(void)
virtual void CycleFiltersByPriority()
Definition: streamhandler.h:96
virtual void SetRunningDesired(bool desired)
At minimum this sets _running_desired, this may also send signals to anything that might be blocking ...
void UpdateListeningForEIT(void)
QString m_device
PIDInfoMap m_pidInfo
ThreadedFileWriter * m_mptsTfw
volatile bool m_runningDesired
volatile bool m_bError
bool RemoveAllPIDFilters(void)
void SetRunning(bool running, bool using_buffering, bool using_section_reader)
QMutex m_startStopLock
bool m_usingSectionReader
bool UpdateFiltersFromStreamData(void)
QMutex m_addRmLock
bool IsRunning(void) const
std::vector< uint > m_eitPids
PIDPriority GetPIDPriority(uint pid) const
QSet< QString > m_mptsFiles
~StreamHandler() override
void Stop(void)
QWaitCondition m_runningStateChanged
virtual void RemoveListener(MPEGStreamData *data)
virtual void AddListener(MPEGStreamData *data, bool allow_section_reader=false, bool needs_buffering=false, const QString &output_file=QString())
bool m_allowSectionReader
virtual bool UpdateFilters(void)
Definition: streamhandler.h:95
virtual PIDInfo * CreatePIDInfo(uint pid, uint stream_type, int pes_type)
QRecursiveMutex m_listenerLock
@ PrivSec
ISO 13818-1 private tables & ITU H.222.0.
Definition: mpegtables.h:146
This class supports the writing of recordings to disk.
bool Open(void)
Opens the file we will be writing to.
int Write(const void *data, uint count)
Writes data to the end of the write buffer.
unsigned int uint
Definition: compat.h:60
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
QMap< uint, PIDPriority > pid_map_t
PIDPriority
@ kPIDPriorityNone
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
dictionary info
Definition: azlyrics.py:7
#define LOC
#define O_LARGEFILE