MythTV master
dtvrecorder.cpp
Go to the documentation of this file.
1
23#ifndef __cpp_size_t_suffix
25#endif
26
27#include "bytereader.h"
28#include "dtvrecorder.h"
29#include "io/mythmediabuffer.h"
30#include "mpeg/AVCParser.h"
31#include "mpeg/HEVCParser.h"
32#include "mpeg/atscstreamdata.h"
33#include "mpeg/dvbstreamdata.h"
34#include "mpeg/mpegstreamdata.h"
35#include "mpeg/mpegtables.h"
36#include "mythsystemevent.h"
37#include "programinfo.h"
38#include "tv_rec.h"
39
40#define LOC ((m_tvrec) ? \
41 QString("DTVRec[%1]: ").arg(m_tvrec->GetInputId()) : \
42 QString("DTVRec(0x%1): ").arg(intptr_t(this),0,16))
43
45
55 RecorderBase(rec),
56 m_h2645Parser(reinterpret_cast<H2645Parser *>(new AVCParser))
57{
59#ifdef __cpp_size_t_suffix
60 m_payloadBuffer.reserve(TSPacket::kSize * (50UZ + 1));
61#else
62 m_payloadBuffer.reserve(TSPacket::kSize * (50_UZ + 1));
63#endif
64
66
68 gCoreContext->GetNumSetting("MinimumRecordingQuality", 95);
69
71}
72
74{
76
78
79 if (m_inputPat)
80 {
81 delete m_inputPat;
82 m_inputPat = nullptr;
83 }
84
85 if (m_inputPmt)
86 {
87 delete m_inputPmt;
88 m_inputPmt = nullptr;
89 }
90
91 if (m_h2645Parser)
92 {
93 delete m_h2645Parser;
94 m_h2645Parser = nullptr;
95 }
96}
97
98void DTVRecorder::SetOption(const QString &name, const QString &value)
99{
100 if (name == "recordingtype")
101 m_recordingType = value;
102 else
103 RecorderBase::SetOption(name, value);
104}
105
109void DTVRecorder::SetOption(const QString &name, int value)
110{
111 if (name == "wait_for_seqstart")
112 m_waitForKeyframeOption = (value == 1);
113 else if (name == "recordmpts")
114 m_recordMpts = (value != 0);
115 else
116 RecorderBase::SetOption(name, value);
117}
118
120 const QString &videodev,
121 const QString& /*audiodev*/, const QString& /*vbidev*/)
122{
123 SetOption("videodevice", videodev);
124 DTVRecorder::SetOption("tvformat", gCoreContext->GetSetting("TVFormat"));
125 SetStrOption(profile, "recordingtype");
126 SetIntOption(profile, "recordmpts");
127}
128
134{
135 if (m_ringBuffer)
137
138 if (m_curRecording)
139 {
142 }
143
145}
146
148{
149 LOG(VB_RECORD, LOG_INFO, LOC + "ResetForNewFile(void)");
150 QMutexLocker locker(&m_positionMapLock);
151
152 // m_seen_psp and m_h2645_parser should
153 // not be reset here. This will only be called just as
154 // we're seeing the first packet of a new keyframe for
155 // writing to the new file and anything that makes the
156 // recorder think we're waiting on another keyframe will
157 // send significant amounts of good data to /dev/null.
158 // -- Daniel Kristjansson 2011-02-26
159
160 m_startCode = 0xffffffff;
161 m_firstKeyframe = -1;
164 m_lastGopSeen = 0;
165 m_lastSeqSeen = 0;
169 //_recording
170 m_error = QString();
171
173 m_repeatPict = 0;
174
175 //m_pes_synced
176 //m_seen_sps
177 m_positionMap.clear();
178 m_positionMapDelta.clear();
179 m_durationMap.clear();
180 m_durationMapDelta.clear();
181
182 m_primaryVideoCodec = AV_CODEC_ID_NONE;
183 m_primaryAudioCodec = AV_CODEC_ID_NONE;
184
185 m_continuityCounter.fill(0xff);
186
187 locker.unlock();
189}
190
192{
194
195 m_tsCount.fill(0);
196 m_tsLast.fill(-1LL);
197 m_tsFirst.fill(-1LL);
198 //m_tsFirst_dt -- doesn't need to be cleared only used if m_tsFirst>=0
199 m_packetCount.fetchAndStoreRelaxed(0);
200 m_continuityErrorCount.fetchAndStoreRelaxed(0);
203 m_totalDuration = 0;
204 m_tdBase = 0;
205 m_tdTickCount = 0;
207
208}
209
210// documented in recorderbase.h
212{
213 LOG(VB_RECORD, LOG_INFO, LOC + "Reset(void)");
215
216 m_startCode = 0xffffffff;
217
218 if (m_curRecording)
219 {
222 }
223}
224
226{
227 if (data == m_streamData)
228 return;
229
230 MPEGStreamData *old_data = m_streamData;
231 m_streamData = data;
232 delete old_data;
233
234 if (m_streamData)
236}
237
239{
240 if (m_streamData == nullptr)
241 return;
242
245
246 auto *dvb = dynamic_cast<DVBStreamData*>(m_streamData);
247 if (dvb)
248 dvb->AddDVBMainListener(this);
249
250 auto *atsc = dynamic_cast<ATSCStreamData*>(m_streamData);
251
252 if (atsc && atsc->DesiredMinorChannel())
253 {
254 atsc->SetDesiredChannel(atsc->DesiredMajorChannel(),
255 atsc->DesiredMinorChannel());
256 }
257 // clang-tidy assumes that if the result of dynamic_cast is
258 // nullptr that the thing being casted must also be a nullptr.
259 // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
260 else if (m_streamData->DesiredProgram() >= 0)
261 {
263 }
264}
265
266void DTVRecorder::BufferedWrite(const TSPacket &tspacket, bool insert)
267{
268 if (!insert) // PAT/PMT may need inserted in front of any buffered data
269 {
270 // delay until first GOP to avoid decoder crash on res change
272 m_firstKeyframe < 0)
273 return;
274
275 if (m_curRecording && m_timeOfFirstDataIsSet.testAndSetRelaxed(0,1))
276 {
277 QMutexLocker locker(&m_statisticsLock);
281 }
282
283 int val = m_timeOfLatestDataCount.fetchAndAddRelaxed(1);
284 int thresh = m_timeOfLatestDataPacketInterval.fetchAndAddRelaxed(0);
285 if (val > thresh)
286 {
287 QMutexLocker locker(&m_statisticsLock);
288 std::chrono::milliseconds elapsed = m_timeOfLatestDataTimer.restart();
289 int interval = thresh;
290 if (elapsed > kTimeOfLatestDataIntervalTarget + 250ms)
291 {
293 .fetchAndStoreRelaxed(thresh * 4 / 5);
294 }
295 else if (elapsed + 250ms < kTimeOfLatestDataIntervalTarget)
296 {
298 .fetchAndStoreRelaxed(thresh * 9 / 8);
299 }
300
301 m_timeOfLatestDataCount.fetchAndStoreRelaxed(1);
303
304 LOG(VB_RECORD, LOG_DEBUG, LOC +
305 QString("Updating timeOfLatestData elapsed(%1) interval(%2)")
306 .arg(elapsed.count()).arg(interval));
307 }
308
309 // Do we have to buffer the packet for exact keyframe detection?
310 if (m_bufferPackets)
311 {
312 int idx = m_payloadBuffer.size();
313 m_payloadBuffer.resize(idx + TSPacket::kSize);
314 memcpy(&m_payloadBuffer[idx], tspacket.data(), TSPacket::kSize);
315 return;
316 }
317
318 // We are free to write the packet, but if we have buffered packet[s]
319 // we have to write them first...
320 if (!m_payloadBuffer.empty())
321 {
322 if (m_ringBuffer)
324 m_payloadBuffer.clear();
325 }
326 }
327
328 if (m_ringBuffer && m_ringBuffer->Write(tspacket.data(), TSPacket::kSize) < 0 &&
330 {
331 LOG(VB_GENERAL, LOG_INFO, LOC +
332 QString("BufferedWrite: Writes are failing, "
333 "setting status to %1")
335 SetRecordingStatus(RecStatus::Failing, __FILE__, __LINE__);
336 }
337}
338
339enum : std::uint8_t { kExtractPTS, kExtractDTS };
340static int64_t extract_timestamp(
341 const uint8_t *bufptr, int bytes_left, int pts_or_dts)
342{
343 if (bytes_left < 4)
344 return -1LL;
345
346 bool has_pts = (bufptr[3] & 0x80) != 0;
347 int offset = 5;
348 if (((kExtractPTS == pts_or_dts) && !has_pts) || (offset + 5 > bytes_left))
349 return -1LL;
350
351 bool has_dts = (bufptr[3] & 0x40) != 0;
352 if (kExtractDTS == pts_or_dts)
353 {
354 if (!has_dts)
355 return -1LL;
356 offset += has_pts ? 5 : 0;
357 if (offset + 5 > bytes_left)
358 return -1LL;
359 }
360
361 return ((uint64_t(bufptr[offset+0] & 0x0e) << 29) |
362 (uint64_t(bufptr[offset+1] ) << 22) |
363 (uint64_t(bufptr[offset+2] & 0xfe) << 14) |
364 (uint64_t(bufptr[offset+3] ) << 7) |
365 (uint64_t(bufptr[offset+4] & 0xfe) >> 1));
366}
367
368static QDateTime ts_to_qdatetime(
369 uint64_t pts, uint64_t pts_first, const QDateTime &pts_first_dt)
370{
371 if (pts < pts_first)
372 pts += 0x1FFFFFFFFLL;
373 const QDateTime& dt = pts_first_dt;
374 return dt.addMSecs((pts - pts_first)/90);
375}
376
377static const std::array<const MythAVRational,16> frameRateMap = {
378 MythAVRational(0), MythAVRational(24000, 1001), MythAVRational(24),
379 MythAVRational(25), MythAVRational(30000, 1001), MythAVRational(30),
380 MythAVRational(50), MythAVRational(60000, 1001), MythAVRational(60),
384};
385
413{
414 if (!tspacket->HasPayload()) // no payload to scan
415 return m_firstKeyframe >= 0;
416
417 if (!m_ringBuffer)
418 return m_firstKeyframe >= 0;
419
420 // if packet contains start of PES packet, start
421 // looking for first byte of MPEG start code (3 bytes 0 0 1)
422 // otherwise, pick up search where we left off.
423 const bool payloadStart = tspacket->PayloadStart();
424 m_startCode = (payloadStart) ? 0xffffffff : m_startCode;
425
426 // Just make these local for efficiency reasons (gcc not so smart..)
427 const uint maxKFD = kMaxKeyFrameDistance;
428 bool hasFrame = false;
429 bool hasKeyFrame = false;
430
431 uint aspectRatio = 0;
432 uint height = 0;
433 uint width = 0;
434 MythAVRational frameRate {0};
435
436 // Scan for PES header codes; specifically picture_start
437 // sequence_start (SEQ) and group_start (GOP).
438 // 00 00 01 00: picture_start_code
439 // 00 00 01 B8: group_start_code
440 // 00 00 01 B3: seq_start_code
441 // 00 00 01 B5: ext_start_code
442 // (there are others that we don't care about)
443 const uint8_t *bufptr = tspacket->data() + tspacket->AFCOffset();
444 const uint8_t *bufend = tspacket->data() + TSPacket::kSize;
445 m_repeatPict = 0;
446
447 while (bufptr < bufend)
448 {
449 bufptr = ByteReader::find_start_code_truncated(bufptr, bufend, &m_startCode);
450 int bytes_left = bufend - bufptr;
452 {
453 // At this point we have seen the start code 0 0 1
454 // the next byte will be the PES packet stream id.
455 const int stream_id = m_startCode & 0x000000ff;
456 if (PESStreamID::PictureStartCode == stream_id)
457 {
459 {
460 hasFrame = true;
461 }
462 // else deterimine hasFrame from the following picture_coding_extension()
463 }
464 else if (PESStreamID::GOPStartCode == stream_id)
465 {
467 hasKeyFrame = true;
468 }
469 else if (PESStreamID::SequenceStartCode == stream_id)
470 {
472 hasKeyFrame |= (m_lastGopSeen + maxKFD)<m_framesSeenCount;
473
474 // Look for aspectRatio changes and store them in the database
475 aspectRatio = (bufptr[3] >> 4);
476
477 // Get resolution
478 height = ((bufptr[1] & 0xf) << 8) | bufptr[2];
479 width = (bufptr[0] <<4) | (bufptr[1]>>4);
480
481 frameRate = frameRateMap[(bufptr[3] & 0x0000000f)];
482 }
483 else if (PESStreamID::MPEG2ExtensionStartCode == stream_id)
484 {
485 if (bytes_left >= 1)
486 {
487 int ext_type = (bufptr[0] >> 4);
488 switch(ext_type)
489 {
490 case 0x1: /* sequence extension */
491 if (bytes_left >= 6)
492 {
493 m_progressiveSequence = bufptr[1] & (1 << 3);
494 }
495 break;
496 case 0x8: /* picture coding extension */
497 if (bytes_left >= 5)
498 {
499 int picture_structure = bufptr[2] & 3;
500 int top_field_first = bufptr[3] & (1 << 7);
501 int repeat_first_field = bufptr[3] & (1 << 1);
502 int progressive_frame = bufptr[4] & (1 << 7);
503#if 0
504 LOG(VB_RECORD, LOG_DEBUG, LOC +
505 QString("picture_coding_extension(): (m_progressiveSequence: %1) picture_structure: %2 top_field_first: %3 repeat_first_field: %4 progressive_frame: %5")
506 .arg(QString::number(m_progressiveSequence , 2),
507 QString::number(picture_structure , 2),
508 QString::number(top_field_first , 2),
509 QString::number(repeat_first_field , 2),
510 QString::number(progressive_frame , 2)
511 )
512 );
513#endif
515 {
516 if (picture_structure == 0b00)
517 {
518 ; // reserved
519 }
520 else if (picture_structure == 0b11)
521 {
522 // frame picture (either interleaved interlaced or progressive)
523 hasFrame = true;
524 }
525 else if (picture_structure < 0b11)
526 {
527 // field picture
528 // Only add a frame for the first presented field.
529 // Do not add a frame for each field picture.
530 if (top_field_first != 0)
531 {
532 hasFrame = (picture_structure == 0b01); // Top Field
533 }
534 else // top_field_first == 0
535 {
536 hasFrame = (picture_structure == 0b10); // Bottom Field
537 }
538 }
539 }
540
541 /* check if we must repeat the frame */
542 m_repeatPict = 1;
543 if (repeat_first_field)
544 {
546 {
547 if (top_field_first)
548 m_repeatPict = 5;
549 else
550 m_repeatPict = 3;
551 }
552 else if (progressive_frame)
553 {
554 m_repeatPict = 2;
555 }
556 }
557 // The m_repeatPict code above matches
558 // mpegvideo_extract_headers(), but the
559 // code in mpeg_field_start() computes a
560 // value one less, which seems correct.
561 --m_repeatPict;
562 }
563 break;
564 }
565 }
566 }
567 if ((stream_id >= PESStreamID::MPEGVideoStreamBegin) &&
568 (stream_id <= PESStreamID::MPEGVideoStreamEnd))
569 {
570 int64_t pts = extract_timestamp(
571 bufptr, bytes_left, kExtractPTS);
572 int64_t dts = extract_timestamp(
573 bufptr, bytes_left, kExtractPTS);
574 HandleTimestamps(stream_id, pts, dts);
575 // Detect music choice program (very slow frame rate and audio)
576 if (m_firstKeyframe < 0
577 && m_tsLast[stream_id] - m_tsFirst[stream_id] > 3*90000LL)
578 {
579 hasKeyFrame = true;
580 m_musicChoice = true;
581 LOG(VB_GENERAL, LOG_INFO, LOC + "Music Choice program detected");
582 }
583 }
584 }
585 }
586
587 if (hasFrame && !hasKeyFrame)
588 {
589 // If we have seen kMaxKeyFrameDistance frames since the
590 // last GOP or SEQ stream_id, then pretend this picture
591 // is a keyframe. We may get artifacts but at least
592 // we will be able to skip frames.
593 hasKeyFrame = ((m_framesSeenCount & 0xf) == 0U);
594 hasKeyFrame &= (m_lastGopSeen + maxKFD) < m_framesSeenCount;
595 hasKeyFrame &= (m_lastSeqSeen + maxKFD) < m_framesSeenCount;
596 }
597
598 // m_bufferPackets will only be true if a payload start has been seen
599 if (hasKeyFrame && (m_bufferPackets || m_firstKeyframe >= 0))
600 {
601 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
602 ("Keyframe @ %1 + %2 = %3")
604 .arg(m_payloadBuffer.size())
606
609 }
610
611 if (hasFrame)
612 {
613 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
614 ("Frame @ %1 + %2 = %3")
616 .arg(m_payloadBuffer.size())
618
619 m_bufferPackets = false; // We now know if it is a keyframe, or not
623 else
624 {
625 /* Found a frame that is not a keyframe, and we want to
626 * start on a keyframe */
627 m_payloadBuffer.clear();
628 }
629 }
630
631 if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
632 {
633 m_videoAspect = aspectRatio;
635 }
636
637 if (height && width && (height != m_videoHeight || m_videoWidth != width))
638 {
639 m_videoHeight = height;
640 m_videoWidth = width;
642 }
643
644 if (frameRate.isNonzero() && frameRate != m_frameRate)
645 {
646 m_frameRate = frameRate;
647 LOG(VB_RECORD, LOG_INFO, LOC +
648 QString("FindMPEG2Keyframes: frame rate = %1")
649 .arg(frameRate.toDouble() * 1000));
650 FrameRateChange(frameRate.toDouble() * 1000, m_framesWrittenCount);
651 }
652
653 return m_firstKeyframe >= 0;
654}
655
656void DTVRecorder::HandleTimestamps(int stream_id, int64_t pts, int64_t dts)
657{
658 if (pts < 0)
659 {
660 m_tsLast[stream_id] = -1;
661 return;
662 }
663
664 if ((dts < 0) && !m_usePts)
665 {
666 m_tsLast[stream_id] = -1;
667 m_usePts = true;
668 LOG(VB_RECORD, LOG_DEBUG,
669 "Switching from dts tracking to pts tracking." +
670 QString("TS count is %1").arg(m_tsCount[stream_id]));
671 }
672
673 int64_t ts = dts;
674 int64_t gap_threshold = 90000; // 1 second
675 if (m_usePts)
676 {
677 ts = dts;
678 gap_threshold = 2*90000LL; // two seconds, compensate for GOP ordering
679 }
680
681 if (m_musicChoice)
682 gap_threshold = 8*90000LL; // music choice uses frames every 6 seconds
683
684 if (m_tsLast[stream_id] >= 0)
685 {
686 int64_t diff = ts - m_tsLast[stream_id];
687
688 // time jumped back more then 10 seconds, handle it as 33bit overflow
689 if (diff < (10 * -90000LL))
690 // MAX_PTS is 33bits all 1
691 diff += 0x1ffffffffLL;
692
693 // FIXME why do we handle negative gaps (aka overlap) like a gap?
694 if (diff < 0)
695 diff = -diff;
696
697 if (diff > gap_threshold && m_firstKeyframe >= 0)
698 {
699 QMutexLocker locker(&m_statisticsLock);
700
701 m_recordingGaps.push_back(
704 m_tsLast[stream_id], m_tsFirst[stream_id],
705 m_tsFirstDt[stream_id]),
707 ts, m_tsFirst[stream_id], m_tsFirstDt[stream_id])));
708 LOG(VB_RECORD, LOG_DEBUG, LOC + QString("Inserted gap %1 dur %2")
709 .arg(m_recordingGaps.back().toString()).arg(diff/90000.0));
710
712 {
714 if (recq.IsDamaged())
715 {
716 LOG(VB_GENERAL, LOG_INFO, LOC +
717 QString("HandleTimestamps: too much damage, "
718 "setting status to %1")
720 SetRecordingStatus(RecStatus::Failing, __FILE__, __LINE__);
721 }
722 }
723 }
724 }
725
726 m_tsLast[stream_id] = ts;
727
728 if (m_tsCount[stream_id] < 30)
729 {
730 if (!m_tsCount[stream_id] || (ts < m_tsFirst[stream_id]))
731 {
732 m_tsFirst[stream_id] = ts;
733 m_tsFirstDt[stream_id] = MythDate::current();
734 }
735 }
736
737 m_tsCount[stream_id]++;
738}
739
741{
746 {
748 m_tdTickCount = 0;
750 }
753 {
754 // not 1000 since m_tdTickCount needs to be divided by 2 to get an equivalent frame count
755 m_totalDuration = m_tdBase + ((int64_t) 500 * m_tdTickCount *
757 }
758
759 if (m_framesWrittenCount < 2000 || m_framesWrittenCount % 1000 == 0)
760 {
761 LOG(VB_RECORD, LOG_DEBUG,
762 QString("count=%1 m_frameRate=%2 tick_frameRate=%3 "
763 "tick_cnt=%4 tick_base=%5 _total_dur=%6")
765 .arg(m_frameRate.toString(),
767 .arg(m_tdTickCount)
768 .arg(m_tdBase)
769 .arg(m_totalDuration));
770 }
771}
772
774{
775 bool hasKeyFrame = false;
776 if (!m_ringBuffer || (GetStreamData()->VideoPIDSingleProgram() <= 0x1fff))
777 return hasKeyFrame;
778
779 static constexpr uint64_t kMsecPerDay { 24ULL * 60 * 60 * 1000 };
780 const double frame_interval = (1000.0 / m_videoFrameRate);
781 uint64_t elapsed = 0;
782 if (m_audioTimer.isValid())
783 elapsed = m_audioTimer.elapsed();
784 auto expected_frame = (uint64_t) ((double)elapsed / frame_interval);
785
786 while (m_framesSeenCount > expected_frame + 10000)
787 expected_frame += (uint64_t) ((double)kMsecPerDay / frame_interval);
788
789 if (!m_framesSeenCount || (m_framesSeenCount < expected_frame))
790 {
792 m_audioTimer.start();
793
794 m_bufferPackets = false;
796
797 if (1 == (m_framesSeenCount & 0x7))
798 {
801 hasKeyFrame = true;
802 }
803
806 }
807
808 return hasKeyFrame;
809}
810
814{
815 if (!m_ringBuffer || (GetStreamData()->VideoPIDSingleProgram() <= 0x1fff))
816 return true;
817
819 return true;
820
821 LOG(VB_RECORD, LOG_INFO, LOC + "DSMCC - FindOtherKeyframes() - "
822 "generating initial key-frame");
823
827
829
831
832 return true;
833}
834
840{
841 if (!m_ringBuffer)
842 return;
843
844 // Perform ringbuffer switch if needed.
846
847 uint64_t frameNum = m_framesWrittenCount;
848 if (m_firstKeyframe < 0)
849 {
850 m_firstKeyframe = frameNum;
851 SendMythSystemRecEvent("REC_STARTED_WRITING", m_curRecording);
852 }
853
854 // Add key frame to position map
855 m_positionMapLock.lock();
856 if (!m_positionMap.contains(frameNum))
857 {
858 int64_t startpos = m_ringBuffer->GetWritePosition() + extra;
859
860 // Don't put negative offsets into the database, they get munged into
861 // MAX_INT64 - offset, which is an exceedingly large number, and
862 // certainly not valid.
863 if (startpos >= 0)
864 {
865 m_positionMapDelta[frameNum] = startpos;
866 m_positionMap[frameNum] = startpos;
867 m_durationMap[frameNum] = llround(m_totalDuration);
868 m_durationMapDelta[frameNum] = llround(m_totalDuration);
869 }
870 }
871 m_positionMapLock.unlock();
872}
873
880{
881 if (!tspacket->HasPayload()) // no payload to scan
882 return m_firstKeyframe >= 0;
883
884 if (!m_ringBuffer)
885 {
886 LOG(VB_GENERAL, LOG_ERR, LOC + "FindH2645Keyframes: No ringbuffer");
887 return m_firstKeyframe >= 0;
888 }
889
890 if (!m_h2645Parser)
891 {
892 LOG(VB_GENERAL, LOG_ERR, LOC + "FindH2645Keyframes: m_h2645Parser not present");
893 return m_firstKeyframe >= 0;
894 }
895
896 const bool payloadStart = tspacket->PayloadStart();
897 if (payloadStart)
898 {
899 // reset PES sync state
900 m_pesSynced = false;
901 m_startCode = 0xffffffff;
902 }
903
904 uint aspectRatio = 0;
905 uint height = 0;
906 uint width = 0;
907 MythAVRational frameRate {0};
909
910 bool hasFrame = false;
911 bool hasKeyFrame = false;
912
913 // scan for PES packets and H.264 NAL units
914 uint i = tspacket->AFCOffset();
915 for (; i < TSPacket::kSize; ++i)
916 {
917 // special handling required when a new PES packet begins
918 if (payloadStart && !m_pesSynced)
919 {
920 // bounds check
921 if (i + 2 >= TSPacket::kSize)
922 {
923 LOG(VB_GENERAL, LOG_ERR, LOC +
924 "PES packet start code may overflow to next TS packet, "
925 "aborting keyframe search");
926 break;
927 }
928
929 // must find the PES start code
930 if (tspacket->data()[i ] != 0x00 ||
931 tspacket->data()[i+1] != 0x00 ||
932 tspacket->data()[i+2] != 0x01)
933 {
934 if (!m_pesTimer.isRunning() || m_pesTimer.elapsed() > 20000ms)
935 {
937 LOG(VB_GENERAL, LOG_ERR, LOC +
938 "PES start code not found in TS packet with PUSI set");
939 }
940 break;
941 }
942 i += 3;
943
945
946 // bounds check
947 if (i + 5 >= TSPacket::kSize)
948 {
949 LOG(VB_GENERAL, LOG_ERR, LOC +
950 "PES packet headers overflow to next TS packet, "
951 "aborting keyframe search");
952 break;
953 }
954
955 // now we need to compute where the PES payload begins
956 // skip past the stream_id (+1)
957 // the next two bytes are the PES packet length (+2)
958 // after that, one byte of PES packet control bits (+1)
959 // after that, one byte of PES header flags bits (+1)
960 // and finally, one byte for the PES header length
961 const unsigned char pes_header_length = tspacket->data()[i + 5];
962
963 // bounds check
964 if ((i + 6 + pes_header_length) >= TSPacket::kSize)
965 {
966 LOG(VB_GENERAL, LOG_ERR, LOC +
967 "PES packet headers overflow to next TS packet, "
968 "aborting keyframe search");
969 break;
970 }
971
972 // we now know where the PES payload is
973 // normally, we should have used 6, but use 5 because the for
974 // loop will bump i
975 i += 5 + pes_header_length;
976 m_pesSynced = true;
977
978#if 0
979 LOG(VB_RECORD, LOG_DEBUG, LOC + "PES synced");
980#endif
981 continue;
982 }
983
984 if (!m_pesSynced)
985 break;
986
987 // scan the NAL units
988 uint32_t bytes_used = m_h2645Parser->addBytes
989 (tspacket->data() + i, TSPacket::kSize - i,
991 i += (bytes_used - 1);
992
994 {
997 {
998 hasKeyFrame = m_h2645Parser->onKeyFrameStart();
999 hasFrame = true;
1000 m_seenSps |= hasKeyFrame;
1001
1002 width = m_h2645Parser->pictureWidth();
1003 height = m_h2645Parser->pictureHeight();
1004 aspectRatio = m_h2645Parser->aspectRatio();
1005 scantype = m_h2645Parser->GetScanType();
1006 frameRate = m_h2645Parser->getFrameRate();
1007 }
1008 }
1009 } // for (; i < TSPacket::kSize; ++i)
1010
1011 // If it has been more than 511 frames since the last keyframe,
1012 // pretend we have one.
1013 if (hasFrame && !hasKeyFrame &&
1015 {
1016 hasKeyFrame = true;
1017 LOG(VB_RECORD, LOG_WARNING, LOC +
1018 QString("FindH2645Keyframes: %1 frames without a keyframe.")
1020 }
1021
1022 // m_bufferPackets will only be true if a payload start has been seen
1023 if (hasKeyFrame && (m_bufferPackets || m_firstKeyframe >= 0))
1024 {
1025 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
1026 ("Keyframe @ %1 + %2 = %3 AU %4")
1028 .arg(m_payloadBuffer.size())
1031
1034 }
1035
1036 if (hasFrame)
1037 {
1038 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
1039 ("Frame @ %1 + %2 = %3 AU %4")
1041 .arg(m_payloadBuffer.size())
1044
1045 m_bufferPackets = false; // We now know if this is a keyframe
1049 else
1050 {
1051 /* Found a frame that is not a keyframe, and we want to
1052 * start on a keyframe */
1053 m_payloadBuffer.clear();
1054 }
1055 }
1056
1057 if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
1058 {
1059 m_videoAspect = aspectRatio;
1061 }
1062
1063 if (height && width && (height != m_videoHeight || m_videoWidth != width))
1064 {
1065 m_videoHeight = height;
1066 m_videoWidth = width;
1067 ResolutionChange(width, height, m_framesWrittenCount);
1068 }
1069
1070 if (frameRate.isNonzero() && frameRate != m_frameRate)
1071 {
1072 LOG(VB_RECORD, LOG_INFO, LOC +
1073 QString("FindH2645Keyframes: timescale: %1, tick: %2, framerate: %3")
1074 .arg( m_h2645Parser->GetTimeScale() )
1075 .arg( m_h2645Parser->GetUnitsInTick() )
1076 .arg( frameRate.toDouble() * 1000 ) );
1077 m_frameRate = frameRate;
1078 FrameRateChange(frameRate.toDouble() * 1000, m_framesWrittenCount);
1079 }
1080
1081 if (scantype != SCAN_t::UNKNOWN_SCAN && scantype != m_scanType)
1082 {
1083 LOG(VB_RECORD, LOG_INFO, LOC +
1084 QString("FindH2645Keyframes: scan type: %1")
1085 .arg(scantype == SCAN_t::INTERLACED ?
1086 "Interlaced" : "Progressive"));
1087 m_scanType = scantype;
1089 }
1090
1091 return m_seenSps;
1092}
1093
1099{
1100 // Perform ringbuffer switch if needed.
1102
1103 uint64_t startpos = 0;
1104 uint64_t frameNum = m_framesWrittenCount;
1105
1106 if (m_firstKeyframe < 0)
1107 {
1108 m_firstKeyframe = frameNum;
1109 startpos = 0;
1110 SendMythSystemRecEvent("REC_STARTED_WRITING", m_curRecording);
1111 }
1112 else
1113 {
1115 }
1116
1117 // Add key frame to position map
1118 m_positionMapLock.lock();
1119 if (!m_positionMap.contains(frameNum))
1120 {
1121 m_positionMapDelta[frameNum] = startpos;
1122 m_positionMap[frameNum] = startpos;
1123 m_durationMap[frameNum] = llround(m_totalDuration);
1124 m_durationMapDelta[frameNum] = llround(m_totalDuration);
1125 }
1126 m_positionMapLock.unlock();
1127}
1128
1129void DTVRecorder::FindPSKeyFrames(const uint8_t *buffer, uint len)
1130{
1131 const uint maxKFD = kMaxKeyFrameDistance;
1132
1133 const uint8_t *bufstart = buffer;
1134 const uint8_t *bufptr = buffer;
1135 const uint8_t *bufend = buffer + len;
1136
1137 uint aspectRatio = 0;
1138 uint height = 0;
1139 uint width = 0;
1140 MythAVRational frameRate {0};
1141
1143 while (bufptr + skip < bufend)
1144 {
1145 bool hasFrame = false;
1146 bool hasKeyFrame = false;
1147
1148 const uint8_t *tmp = bufptr;
1149 bufptr = ByteReader::find_start_code_truncated(bufptr + skip, bufend, &m_startCode);
1152 m_videoBytesRemaining -= std::min(
1153 (uint)(bufptr - tmp), m_videoBytesRemaining);
1154
1156 continue;
1157
1158 // NOTE: Length may be zero for packets that only contain bytes from
1159 // video elementary streams in TS packets. 13818-1:2000 2.4.3.7
1160 int pes_packet_length = -1;
1161 if ((bufend - bufptr) >= 2)
1162 pes_packet_length = ((bufptr[0]<<8) | bufptr[1]) + 2 + 6;
1163
1164 const int stream_id = m_startCode & 0x000000ff;
1166 {
1167 if (PESStreamID::PictureStartCode == stream_id)
1168 { // pes_packet_length is meaningless
1169 pes_packet_length = -1;
1170 if (bufend-bufptr >= 4)
1171 {
1172 uint frmtypei = (bufptr[1]>>3) & 0x7;
1173 if ((1 <= frmtypei) && (frmtypei <= 5))
1174 hasFrame = true;
1175 }
1176 else
1177 {
1178 hasFrame = true;
1179 }
1180 }
1181 else if (PESStreamID::GOPStartCode == stream_id)
1182 { // pes_packet_length is meaningless
1183 pes_packet_length = -1;
1185 hasKeyFrame = true;
1186 }
1187 else if (PESStreamID::SequenceStartCode == stream_id)
1188 { // pes_packet_length is meaningless
1189 pes_packet_length = -1;
1191 hasKeyFrame |= (m_lastGopSeen + maxKFD)<m_framesSeenCount;
1192
1193 // Look for aspectRatio changes and store them in the database
1194 aspectRatio = (bufptr[3] >> 4);
1195
1196 // Get resolution
1197 height = ((bufptr[1] & 0xf) << 8) | bufptr[2];
1198 width = (bufptr[0] <<4) | (bufptr[1]>>4);
1199
1200 frameRate = frameRateMap[(bufptr[3] & 0x0000000f)];
1201 }
1202 }
1203 else if (!m_audioBytesRemaining)
1204 {
1205 if ((stream_id >= PESStreamID::MPEGVideoStreamBegin) &&
1206 (stream_id <= PESStreamID::MPEGVideoStreamEnd))
1207 { // ok-dvdinfo
1208 m_videoBytesRemaining = std::max(0, pes_packet_length);
1209 }
1210 else if ((stream_id >= PESStreamID::MPEGAudioStreamBegin) &&
1211 (stream_id <= PESStreamID::MPEGAudioStreamEnd))
1212 { // ok-dvdinfo
1213 m_audioBytesRemaining = std::max(0, pes_packet_length);
1214 }
1215 }
1216
1217 if (PESStreamID::PaddingStream == stream_id)
1218 { // ok-dvdinfo
1219 m_otherBytesRemaining = std::max(0, pes_packet_length);
1220 }
1221
1222 m_startCode = 0xffffffff; // reset start code
1223
1224 if (hasFrame && !hasKeyFrame)
1225 {
1226 // If we have seen kMaxKeyFrameDistance frames since the
1227 // last GOP or SEQ stream_id, then pretend this picture
1228 // is a keyframe. We may get artifacts but at least
1229 // we will be able to skip frames.
1230 hasKeyFrame = ((m_framesSeenCount & 0xf) == 0U);
1231 hasKeyFrame &= (m_lastGopSeen + maxKFD) < m_framesSeenCount;
1232 hasKeyFrame &= (m_lastSeqSeen + maxKFD) < m_framesSeenCount;
1233 }
1234
1235 if (hasFrame)
1236 {
1240 }
1241
1242 if (hasKeyFrame)
1243 {
1245 HandleKeyframe((int64_t)m_payloadBuffer.size() - (bufptr - bufstart));
1246 }
1247
1248 if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
1249 {
1250 m_videoAspect = aspectRatio;
1252 }
1253
1254 if (height && width &&
1255 (height != m_videoHeight || m_videoWidth != width))
1256 {
1257 m_videoHeight = height;
1258 m_videoWidth = width;
1259 ResolutionChange(width, height, m_framesWrittenCount);
1260 }
1261
1262 if (frameRate.isNonzero() && frameRate != m_frameRate)
1263 {
1264 m_frameRate = frameRate;
1265 LOG(VB_RECORD, LOG_INFO, LOC +
1266 QString("FindPSKeyFrames: frame rate = %1")
1267 .arg(frameRate.toDouble() * 1000));
1268 FrameRateChange(frameRate.toDouble() * 1000, m_framesWrittenCount);
1269 }
1270
1271 if (hasKeyFrame || hasFrame)
1272 {
1273 // We are free to write the packet, but if we have
1274 // buffered packet[s] we have to write them first...
1275 if (!m_payloadBuffer.empty())
1276 {
1277 if (m_ringBuffer)
1278 {
1280 (m_payloadBuffer).data(), m_payloadBuffer.size());
1281 }
1282 m_payloadBuffer.clear();
1283 }
1284
1285 if (m_ringBuffer)
1286 m_ringBuffer->Write(bufstart, (bufptr - bufstart));
1287
1288 bufstart = bufptr;
1289 }
1290
1292 }
1293
1294 int bytes_skipped = bufend - bufptr;
1295 if (bytes_skipped > 0)
1296 {
1297 m_audioBytesRemaining -= std::min(
1298 (uint)bytes_skipped, m_audioBytesRemaining);
1299 m_videoBytesRemaining -= std::min(
1300 (uint)bytes_skipped, m_videoBytesRemaining);
1301 m_otherBytesRemaining -= std::min(
1302 (uint)bytes_skipped, m_otherBytesRemaining);
1303 }
1304
1305 uint64_t idx = m_payloadBuffer.size();
1306 uint64_t rem = (bufend - bufstart);
1307 m_payloadBuffer.resize(idx + rem);
1308 memcpy(&m_payloadBuffer[idx], bufstart, rem);
1309#if 0
1310 LOG(VB_GENERAL, LOG_DEBUG, LOC +
1311 QString("idx: %1, rem: %2").arg(idx).arg(rem));
1312#endif
1313}
1314
1316{
1317 if (!_pat)
1318 {
1319 LOG(VB_RECORD, LOG_ERR, LOC + "SetPAT(NULL)");
1320 return;
1321 }
1322
1323 QMutexLocker change_lock(&m_pidLock);
1324
1325 int progNum = m_streamData->DesiredProgram();
1326 uint pmtpid = _pat->FindPID(progNum);
1327
1328 // If we have not found the desired program in the PAT and this happens to be
1329 // an SPTS then update the desired program to the one that is present in the PAT.
1330 if (!pmtpid)
1331 {
1332 if (_pat->ProgramCount() == 1)
1333 {
1334 int oldProgNum = progNum;
1335 progNum = _pat->ProgramNumber(0);
1336 LOG(VB_GENERAL, LOG_INFO, LOC +
1337 QString("Update desired program found in SPTS PAT from %1 to %2")
1338 .arg(oldProgNum).arg(progNum));
1339 GetStreamData()->SetDesiredProgram(progNum);
1340 pmtpid = _pat->FindPID(progNum);
1341 }
1342 }
1343 if (!pmtpid)
1344 {
1345 LOG(VB_RECORD, LOG_ERR, LOC +
1346 QString("SetPAT(): Ignoring PAT not containing our desired "
1347 "program (%1)...").arg(progNum));
1348 return;
1349 }
1350
1351 LOG(VB_RECORD, LOG_INFO, LOC + QString("SetPAT(%1 on pid 0x%2)")
1352 .arg(progNum).arg(pmtpid,0,16));
1353
1356 delete oldpat;
1357
1358 // Listen for the other PMTs for faster channel switching
1359 for (uint i = 0; m_inputPat && (i < m_inputPat->ProgramCount()); ++i)
1360 {
1361 uint pmt_pid = m_inputPat->ProgramPID(i);
1362 if (!m_streamData->IsListeningPID(pmt_pid))
1364 }
1365}
1366
1368{
1369 QMutexLocker change_lock(&m_pidLock);
1370
1371 LOG(VB_RECORD, LOG_INFO, LOC + QString("SetPMT(%1, %2)").arg(progNum)
1372 .arg(_pmt == nullptr ? "NULL" : "valid"));
1373
1374
1375 if ((int)progNum == m_streamData->DesiredProgram())
1376 {
1377 LOG(VB_RECORD, LOG_INFO, LOC + QString("SetPMT(%1)").arg(progNum));
1378 ProgramMapTable *oldpmt = m_inputPmt;
1379 m_inputPmt = new ProgramMapTable(*_pmt);
1380
1381 QString sistandard = GetSIStandard();
1382
1383 bool has_no_av = true;
1384 for (uint i = 0; i < m_inputPmt->StreamCount() && has_no_av; ++i)
1385 {
1386 has_no_av &= !m_inputPmt->IsVideo(i, sistandard);
1387 has_no_av &= !m_inputPmt->IsAudio(i, sistandard);
1388 }
1389 m_hasNoAV = has_no_av;
1390
1392 delete oldpmt;
1393 }
1394}
1395
1397 bool insert)
1398{
1399 if (!pat)
1400 {
1401 LOG(VB_RECORD, LOG_ERR, LOC + "HandleSingleProgramPAT(NULL)");
1402 return;
1403 }
1404
1405 if (!m_ringBuffer)
1406 return;
1407
1408 uint next_cc = (pat->tsheader()->ContinuityCounter()+1)&0xf;
1409 pat->tsheader()->SetContinuityCounter(next_cc);
1410 pat->GetAsTSPackets(m_scratch, next_cc);
1411
1412 for (const auto & tspacket : m_scratch)
1413 DTVRecorder::BufferedWrite(tspacket, insert);
1414}
1415
1417{
1418 if (!pmt)
1419 {
1420 LOG(VB_RECORD, LOG_ERR, LOC + "HandleSingleProgramPMT(NULL)");
1421 return;
1422 }
1423
1424 // We only want to do these checks once per recording
1425 bool seenVideo = (m_primaryVideoCodec != AV_CODEC_ID_NONE);
1426 bool seenAudio = (m_primaryAudioCodec != AV_CODEC_ID_NONE);
1427 uint bestAudioCodec = 0;
1428 // collect stream types for H.264 (MPEG-4 AVC) keyframe detection
1429 for (uint i = 0; i < pmt->StreamCount(); ++i)
1430 {
1431 // We only care about the first identifiable video stream
1432 if (!seenVideo && (m_primaryVideoCodec == AV_CODEC_ID_NONE) &&
1434 {
1435 seenVideo = true; // Ignore other video streams
1436 switch (pmt->StreamType(i))
1437 {
1439 m_primaryVideoCodec = AV_CODEC_ID_MPEG1VIDEO;
1440 break;
1442 m_primaryVideoCodec = AV_CODEC_ID_MPEG2VIDEO;
1443 break;
1445 m_primaryVideoCodec = AV_CODEC_ID_MPEG4;
1446 break;
1448 m_primaryVideoCodec = AV_CODEC_ID_H264;
1449 if (dynamic_cast<AVCParser *>(m_h2645Parser) == nullptr)
1450 {
1451 delete m_h2645Parser;
1452 m_h2645Parser = nullptr;
1453 }
1454 if (m_h2645Parser == nullptr)
1455 {
1456 auto * avcParser = new AVCParser;
1457 if (avcParser != nullptr)
1458 {
1459 m_h2645Parser = reinterpret_cast<H2645Parser *>
1460 (avcParser);
1461 avcParser->use_I_forKeyframes(m_useIForKeyframe);
1462 }
1463 }
1464 break;
1466 LOG(VB_GENERAL, LOG_INFO, LOC + "HEVC detected");
1467 m_primaryVideoCodec = AV_CODEC_ID_H265;
1468 if (dynamic_cast<HEVCParser *>(m_h2645Parser) == nullptr)
1469 {
1470 delete m_h2645Parser;
1471 m_h2645Parser = nullptr;
1472 }
1473 if (m_h2645Parser == nullptr)
1474 {
1475 m_h2645Parser = reinterpret_cast<H2645Parser *>
1476 (new HEVCParser);
1477 }
1478 break;
1480 m_primaryVideoCodec = AV_CODEC_ID_MPEG2VIDEO; // TODO Will it always be MPEG2?
1481 break;
1482 case StreamID::VC1Video:
1483 m_primaryVideoCodec = AV_CODEC_ID_VC1;
1484 break;
1485 default:
1486 break;
1487 }
1488
1489 if (m_primaryVideoCodec != AV_CODEC_ID_NONE)
1491 }
1492
1493 // We want the 'best' identifiable audio stream, where 'best' is
1494 // subjective and no-one will likely agree.
1495 // For now it's the 'best' codec, assuming mpeg stream types range
1496 // from worst to best, which it does
1497 if (!seenAudio && StreamID::IsAudio(pmt->StreamType(i)) &&
1498 pmt->StreamType(i) > bestAudioCodec)
1499 {
1500 bestAudioCodec = pmt->StreamType(i);
1501 switch (pmt->StreamType(i))
1502 {
1503 case StreamID::MPEG1Audio: // MPEG-1 Layer 2 (MP2)
1504 case StreamID::MPEG2Audio: // MPEG-2 Part 3 (MP2 Multichannel)
1505 m_primaryAudioCodec = AV_CODEC_ID_MP2;
1506 break;
1508 m_primaryAudioCodec = AV_CODEC_ID_AAC;
1509 break;
1511 m_primaryAudioCodec = AV_CODEC_ID_AAC_LATM;
1512 break;
1513 case StreamID::AC3Audio:
1514 m_primaryAudioCodec = AV_CODEC_ID_AC3;
1515 break;
1517 m_primaryAudioCodec = AV_CODEC_ID_EAC3;
1518 break;
1519 case StreamID::DTSAudio:
1520 m_primaryAudioCodec = AV_CODEC_ID_DTS;
1521 break;
1522 default:
1523 break;
1524 }
1525
1526 if (m_primaryAudioCodec != AV_CODEC_ID_NONE)
1528 }
1529
1530// LOG(VB_GENERAL, LOG_DEBUG, QString("Recording(%1): Stream #%2: %3 ")
1531// .arg(m_curRecording ? QString::number(m_curRecording->GetRecordingID()) : "")
1532// .arg(i)
1533// .arg(StreamID::GetDescription(pmt->StreamType(i))));
1534 m_streamId[pmt->StreamPID(i)] = pmt->StreamType(i);
1535 }
1536
1537 // If the PCRPID is valid and the PCR is not contained
1538 // in another stream, make sure the PCR stream is not
1539 // discarded (use PrivSec type as dummy 'valid' value)
1540 if (pmt->PCRPID() != 0x1fff && pmt->FindPID(pmt->PCRPID()) == -1)
1542
1543 if (!m_ringBuffer)
1544 return;
1545
1546 uint next_cc = (pmt->tsheader()->ContinuityCounter()+1)&0xf;
1547 pmt->tsheader()->SetContinuityCounter(next_cc);
1548 pmt->GetAsTSPackets(m_scratch, next_cc);
1549
1550 for (const auto & tspacket : m_scratch)
1551 DTVRecorder::BufferedWrite(tspacket, insert);
1552}
1553
1555{
1556 const uint pid = tspacket.PID();
1557
1558 if (pid != 0x1fff)
1559 m_packetCount.fetchAndAddAcquire(1);
1560
1561 // Check continuity counter
1562 uint old_cnt = m_continuityCounter[pid];
1563 if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter()))
1564 {
1565 int v = m_continuityErrorCount.fetchAndAddRelaxed(1) + 1;
1566 double erate = v * 100.0 / m_packetCount.fetchAndAddRelaxed(0);
1567 LOG(VB_RECORD, LOG_WARNING, LOC +
1568 QString("PID 0x%1 discontinuity detected ((%2+1)%16!=%3) %4%")
1569 .arg(pid,0,16).arg(old_cnt,2)
1570 .arg(tspacket.ContinuityCounter(),2)
1571 .arg(erate));
1572 }
1573
1574 // Only create fake keyframe[s] if there are no audio/video streams
1575 if (m_inputPmt && m_hasNoAV)
1576 {
1577 FindOtherKeyframes(&tspacket);
1578 m_bufferPackets = false;
1579 }
1580 else if (m_recordMptsOnly)
1581 {
1582 /* When recording the full, unfiltered, MPTS, trigger a write
1583 * every 0.5 seconds. Since the packets are unfiltered and
1584 * unprocessed we cannot wait for a keyframe to trigger the
1585 * writes. */
1586
1587 if (m_framesSeenCount++ == 0)
1589
1590 if (m_recordMptsTimer.elapsed() > 0.5s)
1591 {
1596 }
1597 }
1598 else if (m_streamId[pid] == 0)
1599 {
1600 // Ignore this packet if the PID should be stripped
1601 return true;
1602 }
1603 else
1604 {
1605 // There are audio/video streams. Only write the packet
1606 // if audio/video key-frames have been found
1608 return true;
1609 }
1610
1611 BufferedWrite(tspacket);
1612
1613 return true;
1614}
1615
1617{
1618 if (!m_ringBuffer)
1619 return true;
1620
1621 uint streamType = m_streamId[tspacket.PID()];
1622
1623 if (tspacket.HasPayload() && tspacket.PayloadStart())
1624 {
1625 if (m_bufferPackets && m_firstKeyframe >= 0 && !m_payloadBuffer.empty())
1626 {
1627 // Flush the buffer
1628 if (m_ringBuffer)
1630 m_payloadBuffer.clear();
1631 }
1632
1633 // buffer packets until we know if this is a keyframe
1634 m_bufferPackets = true;
1635 }
1636
1637 // Check for keyframes and count frames
1638 if (streamType == StreamID::H264Video ||
1639 streamType == StreamID::H265Video)
1640 FindH2645Keyframes(&tspacket);
1641 else if (streamType != 0)
1642 FindMPEG2Keyframes(&tspacket);
1643 else
1644 LOG(VB_RECORD, LOG_ERR, LOC +
1645 "ProcessVideoTSPacket: unknown stream type!");
1646
1647 return ProcessAVTSPacket(tspacket);
1648}
1649
1651{
1652 if (!m_ringBuffer)
1653 return true;
1654
1655 if (tspacket.HasPayload() && tspacket.PayloadStart())
1656 {
1657 if (m_bufferPackets && m_firstKeyframe >= 0 && !m_payloadBuffer.empty())
1658 {
1659 // Flush the buffer
1660 if (m_ringBuffer)
1662 m_payloadBuffer.clear();
1663 }
1664
1665 // buffer packets until we know if this is a keyframe
1666 m_bufferPackets = true;
1667 }
1668
1669 FindAudioKeyframes(&tspacket);
1670 return ProcessAVTSPacket(tspacket);
1671}
1672
1675{
1676 // Sync recording start to first keyframe
1678 {
1679 if (m_bufferPackets)
1680 BufferedWrite(tspacket);
1681 return true;
1682 }
1683
1684 const uint pid = tspacket.PID();
1685
1686 if (pid != 0x1fff)
1687 m_packetCount.fetchAndAddAcquire(1);
1688
1689 // Check continuity counter
1690 uint old_cnt = m_continuityCounter[pid];
1691 if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter()))
1692 {
1693 int v = m_continuityErrorCount.fetchAndAddRelaxed(1) + 1;
1694 double erate = v * 100.0 / m_packetCount.fetchAndAddRelaxed(0);
1695 LOG(VB_RECORD, LOG_WARNING, LOC +
1696 QString("A/V PID 0x%1 discontinuity detected ((%2+1)%16!=%3) %4%")
1697 .arg(pid,0,16).arg(old_cnt).arg(tspacket.ContinuityCounter())
1698 .arg(erate,5,'f',2));
1699 }
1700
1701 if (!(m_pidStatus[pid] & kPayloadStartSeen))
1702 {
1704 LOG(VB_RECORD, LOG_INFO, LOC +
1705 QString("PID 0x%1 Found Payload Start").arg(pid,0,16));
1706 }
1707
1708 BufferedWrite(tspacket);
1709
1710 return true;
1711}
1712
1714{
1716 recq->AddTSStatistics(
1717 m_continuityErrorCount.fetchAndAddRelaxed(0),
1718 m_packetCount.fetchAndAddRelaxed(0));
1719 return recq;
1720}
1721
1722/* vim: set expandtab tabstop=4 shiftwidth=4: */
This is in libmythtv because that is where the parsers, which are its main users, are.
Encapsulates data about ATSC stream and emits events for most tables.
void SetDesiredChannel(int major, int minor)
bool m_seenSps
Definition: dtvrecorder.h:151
QString m_error
non-empty iff irrecoverable recording error detected
Definition: dtvrecorder.h:161
void FinishRecording(void) override
Flushes the ringbuffer, and if this is not a live LiveTV recording saves the position map and filesiz...
bool m_musicChoice
Definition: dtvrecorder.h:213
bool m_hasWrittenOtherKeyframe
Definition: dtvrecorder.h:157
std::array< QDateTime, 256 > m_tsFirstDt
Definition: dtvrecorder.h:192
uint32_t m_startCode
Definition: dtvrecorder.h:136
bool FindH2645Keyframes(const TSPacket *tspacket)
std::array< uint8_t, 0x1fff+1 > m_continuityCounter
Definition: dtvrecorder.h:183
unsigned int m_audioBytesRemaining
Definition: dtvrecorder.h:141
int m_minimumRecordingQuality
Definition: dtvrecorder.h:187
bool ProcessAudioTSPacket(const TSPacket &tspacket) override
std::vector< unsigned char > m_payloadBuffer
Definition: dtvrecorder.h:167
std::array< int64_t, 256 > m_tsLast
Definition: dtvrecorder.h:190
ProgramMapTable * m_inputPmt
PMT on input side.
Definition: dtvrecorder.h:174
int m_repeatPict
Definition: dtvrecorder.h:147
double m_tdBase
Milliseconds from the start to m_tdTickCount = 0.
Definition: dtvrecorder.h:200
std::vector< TSPacket > m_scratch
Definition: dtvrecorder.h:184
uint64_t m_tdTickCount
Count of the number of equivalent interlaced fields that have passed since m_tdBase.
Definition: dtvrecorder.h:207
std::array< int64_t, 256 > m_tsFirst
Definition: dtvrecorder.h:191
RecordingQuality * GetRecordingQuality(const RecordingInfo *r) const override
Returns a report about the current recordings quality.
unsigned long long m_lastGopSeen
Definition: dtvrecorder.h:138
unsigned long long m_lastKeyframeSeen
Definition: dtvrecorder.h:140
void ResetForNewFile(void) override
~DTVRecorder() override
Definition: dtvrecorder.cpp:73
bool m_bufferPackets
Definition: dtvrecorder.h:166
QElapsedTimer m_audioTimer
Definition: dtvrecorder.h:135
void BufferedWrite(const TSPacket &tspacket, bool insert=false)
int m_progressiveSequence
Definition: dtvrecorder.h:146
bool ProcessVideoTSPacket(const TSPacket &tspacket) override
bool ProcessTSPacket(const TSPacket &tspacket) override
bool FindOtherKeyframes(const TSPacket *tspacket)
Non-Audio/Video data.
virtual void SetCAMPMT(const ProgramMapTable *)
Definition: dtvrecorder.h:125
QString m_recordingType
Definition: dtvrecorder.h:131
unsigned int m_otherBytesRemaining
Definition: dtvrecorder.h:143
DTVRecorder(TVRec *rec)
Definition: dtvrecorder.cpp:54
void HandleH2645Keyframe(void)
This save the current frame to the position maps and handles ringbuffer switching.
MPEGStreamData * GetStreamData(void) const
Definition: dtvrecorder.h:58
unsigned long long m_framesSeenCount
Definition: dtvrecorder.h:195
MythTimer m_pesTimer
Definition: dtvrecorder.h:134
static const unsigned char kPayloadStartSeen
Definition: dtvrecorder.h:222
std::array< uint8_t, 0x1fff+1 > m_streamId
Definition: dtvrecorder.h:181
std::array< uint8_t, 0x1fff+1 > m_pidStatus
Definition: dtvrecorder.h:182
virtual QString GetSIStandard(void) const
Definition: dtvrecorder.h:124
static const uint kMaxKeyFrameDistance
If the number of regular frames detected since the last detected keyframe exceeds this value,...
Definition: dtvrecorder.h:221
bool m_hasNoAV
Definition: dtvrecorder.h:175
std::array< uint64_t, 256 > m_tsCount
Definition: dtvrecorder.h:189
ProgramAssociationTable * m_inputPat
PAT on input side.
Definition: dtvrecorder.h:172
void HandleKeyframe(int64_t extra)
This save the current frame to the position maps and handles ringbuffer switching.
H2645Parser * m_h2645Parser
Definition: dtvrecorder.h:152
void HandleSingleProgramPAT(ProgramAssociationTable *pat, bool insert) override
void HandlePAT(const ProgramAssociationTable *_pat) override
void Reset(void) override
Reset the recorder to the startup state.
int m_firstKeyframe
Definition: dtvrecorder.h:137
MythAVRational m_tdTickFramerate
Definition: dtvrecorder.h:208
bool m_pesSynced
Definition: dtvrecorder.h:150
void HandleTimestamps(int stream_id, int64_t pts, int64_t dts)
void SetOptionsFromProfile(RecordingProfile *profile, const QString &videodev, const QString &audiodev, const QString &vbidev) override
Sets basic recorder options.
QAtomicInt m_packetCount
Definition: dtvrecorder.h:193
bool ProcessAVTSPacket(const TSPacket &tspacket)
Common code for processing either audio or video packets.
void HandlePMT(uint progNum, const ProgramMapTable *_pmt) override
bool m_waitForKeyframeOption
Wait for the a GOP/SEQ-start before sending data.
Definition: dtvrecorder.h:155
MPEGStreamData * m_streamData
Definition: dtvrecorder.h:163
void SetOption(const QString &name, const QString &value) override
Set an specific option.
Definition: dtvrecorder.cpp:98
double m_totalDuration
Total milliseconds that have passed since the start of the recording.
Definition: dtvrecorder.h:198
void FindPSKeyFrames(const uint8_t *buffer, uint len) override
QAtomicInt m_continuityErrorCount
Definition: dtvrecorder.h:194
bool m_useIForKeyframe
Definition: dtvrecorder.h:215
QRecursiveMutex m_pidLock
Definition: dtvrecorder.h:170
bool m_recordMptsOnly
Definition: dtvrecorder.h:179
unsigned int m_videoBytesRemaining
Definition: dtvrecorder.h:142
bool CheckCC(uint pid, uint new_cnt)
Definition: dtvrecorder.h:225
unsigned long long m_lastSeqSeen
Definition: dtvrecorder.h:139
bool FindAudioKeyframes(const TSPacket *tspacket)
bool m_recordMpts
Definition: dtvrecorder.h:178
virtual void SetStreamData(MPEGStreamData *data)
unsigned long long m_framesWrittenCount
Definition: dtvrecorder.h:196
virtual void InitStreamData(void)
MythTimer m_recordMptsTimer
Definition: dtvrecorder.h:180
void HandleSingleProgramPMT(ProgramMapTable *pmt, bool insert) override
bool FindMPEG2Keyframes(const TSPacket *tspacket)
Locates the keyframes and saves them to the position map.
void UpdateFramesWritten(void)
SCAN_t m_scanType
Definition: dtvrecorder.h:209
void ClearStatistics(void) override
void AddDVBMainListener(DVBMainStreamListener *val)
SCAN_t GetScanType(void) const
Definition: H2645Parser.h:80
uint pictureHeight(void) const
Definition: H2645Parser.h:64
uint aspectRatio(void) const
Computes aspect ratio from picture size and sample aspect ratio.
bool stateChanged(void) const
Definition: H2645Parser.h:58
virtual MythAVRational getFrameRate() const =0
virtual field_type getFieldType(void) const =0
bool onFrameStart(void) const
Definition: H2645Parser.h:60
uint pictureWidth(void) const
Definition: H2645Parser.h:63
uint32_t GetUnitsInTick(void) const
Definition: H2645Parser.h:79
virtual uint32_t addBytes(const uint8_t *bytes, uint32_t byte_count, uint64_t stream_offset)=0
bool onKeyFrameStart(void) const
Definition: H2645Parser.h:61
uint32_t GetTimeScale(void) const
Definition: H2645Parser.h:78
uint64_t keyframeAUstreamOffset(void) const
Definition: H2645Parser.h:75
Encapsulates data about MPEG stream and emits events for each table.
void AddMPEGListener(MPEGStreamListener *val)
void SetDesiredProgram(int p)
void AddMPEGSPListener(MPEGSingleProgramStreamListener *val)
int DesiredProgram(void) const
virtual bool IsListeningPID(uint pid) const
virtual void AddListeningPID(uint pid, PIDPriority priority=kPIDPriorityNormal)
C++ wrapper for FFmpeg libavutil AVRational.
double toDouble() const
bool isNonzero() const
QString toString() const
MythAVRational invert() const
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
long long GetWritePosition(void) const
Returns how far into a ThreadedFileWriter file we have written.
void WriterFlush(void)
Calls ThreadedFileWriter::Flush(void)
int Write(const void *Buffer, uint Count)
Writes buffer to ThreadedFileWriter::Write(const void*,uint)
void addMSecs(std::chrono::milliseconds ms)
Adds an offset to the last call to start() or restart().
Definition: mythtimer.cpp:146
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
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
void stop(void)
Stops timer, next call to isRunning() will return false and any calls to elapsed() or restart() will ...
Definition: mythtimer.cpp:78
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
void GetAsTSPackets(std::vector< TSPacket > &output, uint cc) const
Returns payload only PESPacket as series of TSPackets.
Definition: pespacket.cpp:135
const TSHeader * tsheader() const
Definition: pespacket.h:90
@ SequenceStartCode
Sequence (SEQ) start code contains frame size, aspect ratio and fps.
Definition: mpegtables.h:56
@ MPEGAudioStreamEnd
Last MPEG-1/2 audio stream (w/ext hdr)
Definition: mpegtables.h:78
@ MPEG2ExtensionStartCode
Followed by an extension byte, not documented here.
Definition: mpegtables.h:59
@ MPEGVideoStreamBegin
First MPEG-1/2 video stream (w/ext hdr)
Definition: mpegtables.h:80
@ GOPStartCode
Group of Pictures (GOP) start code.
Definition: mpegtables.h:65
@ PictureStartCode
Definition: mpegtables.h:49
@ MPEGVideoStreamEnd
Last MPEG-1/2 video stream (w/ext hdr)
Definition: mpegtables.h:82
@ MPEGAudioStreamBegin
First MPEG-1/2 audio stream (w/ext hdr)
Definition: mpegtables.h:76
The Program Association Table lists all the programs in a stream, and is always found on PID 0.
Definition: mpegtables.h:599
uint ProgramCount(void) const
Definition: mpegtables.h:619
uint FindPID(uint progNum) const
Definition: mpegtables.h:638
uint ProgramNumber(uint i) const
Definition: mpegtables.h:626
uint ProgramPID(uint i) const
Definition: mpegtables.h:629
void ClearPositionMap(MarkTypes type) const
RecStatus::Type GetRecordingStatus(void) const
Definition: programinfo.h:458
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 PCRPID(void) const
stream that contains program clock reference.
Definition: mpegtables.h:709
uint StreamPID(uint i) const
Definition: mpegtables.h:724
uint StreamType(uint i) const
Definition: mpegtables.h:721
int FindPID(uint pid) const
Locates stream index of pid.
Definition: mpegtables.h:780
bool IsAudio(uint i, const QString &sistandard) const
Returns true iff the stream at index i is an audio stream.
Definition: mpegtables.cpp:541
bool IsVideo(uint i, const QString &sistandard) const
Returns true iff the stream at index i is a video stream.
Definition: mpegtables.cpp:519
static QString toString(RecStatus::Type recstatus, uint id)
Converts "recstatus" into a short (unreadable) string.
This is the abstract base class for supporting recorder hardware.
Definition: recorderbase.h:51
QAtomicInt m_timeOfLatestDataPacketInterval
Definition: recorderbase.h:355
static constexpr std::chrono::milliseconds kTimeOfLatestDataIntervalTarget
timeOfLatest update interval target in milliseconds.
Definition: recorderbase.h:360
AVContainer m_containerFormat
Definition: recorderbase.h:295
uint m_videoHeight
Definition: recorderbase.h:307
virtual bool CheckForRingBufferSwitch(void)
If requested, switch to new RingBuffer/ProgramInfo objects.
double m_videoFrameRate
Definition: recorderbase.h:303
frm_pos_map_t m_positionMap
Definition: recorderbase.h:335
AVCodecID m_primaryAudioCodec
Definition: recorderbase.h:297
uint m_videoAspect
Definition: recorderbase.h:305
frm_pos_map_t m_durationMapDelta
Definition: recorderbase.h:338
QDateTime m_timeOfLatestData
Definition: recorderbase.h:356
QMutex m_statisticsLock
Definition: recorderbase.h:351
MythTimer m_timeOfLatestDataTimer
Definition: recorderbase.h:357
void FrameRateChange(uint framerate, uint64_t frame)
Note a change in video frame rate in the recordedmark table.
void SetTotalFrames(uint64_t total_frames)
Note the total frames in the recordedmark table.
QDateTime m_timeOfFirstData
Definition: recorderbase.h:353
void VideoCodecChange(AVCodecID vCodec)
Note a change in video codec.
virtual void StopRecording(void)
StopRecording() signals to the recorder that it should stop recording and exit cleanly.
frm_pos_map_t m_positionMapDelta
Definition: recorderbase.h:336
virtual void FinishRecording(void)
void ResolutionChange(uint width, uint height, long long frame)
Note a change in video size in the recordedmark table.
virtual RecordingQuality * GetRecordingQuality(const RecordingInfo *ri) const
Returns a report about the current recordings quality.
void AspectChange(uint aspect, long long frame)
Note a change in aspect ratio in the recordedmark table.
AVCodecID m_primaryVideoCodec
Definition: recorderbase.h:296
virtual void SetRecordingStatus(RecStatus::Type status, const QString &file, int line)
frm_pos_map_t m_durationMap
Definition: recorderbase.h:337
void SetStrOption(RecordingProfile *profile, const QString &name)
Convenience function used to set QString options from a profile.
MythMediaBuffer * m_ringBuffer
Definition: recorderbase.h:292
RecordingGaps m_recordingGaps
Definition: recorderbase.h:358
void SetDuration(std::chrono::milliseconds duration)
Note the total duration in the recordedmark table.
MythAVRational m_frameRate
Definition: recorderbase.h:309
QAtomicInt m_timeOfLatestDataCount
Definition: recorderbase.h:354
void VideoScanChange(SCAN_t scan, uint64_t frame)
Note a change in video scan type in the recordedmark table.
QAtomicInt m_timeOfFirstDataIsSet
Definition: recorderbase.h:352
QMutex m_positionMapLock
Definition: recorderbase.h:334
virtual void SetOption(const QString &name, const QString &value)
Set an specific option.
void AudioCodecChange(AVCodecID aCodec)
Note a change in audio codec.
RecordingInfo * m_curRecording
Definition: recorderbase.h:311
virtual void ClearStatistics(void)
void SetPositionMapType(MarkTypes type)
Set seektable type.
Definition: recorderbase.h:255
void SetIntOption(RecordingProfile *profile, const QString &name)
Convenience function used to set integer options from a profile.
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:36
bool IsDamaged(void) const
void AddTSStatistics(int continuity_error_count, int packet_count)
@ MPEG2Video
ISO 13818-2 & ITU H.262 (aka MPEG-2)
Definition: mpegtables.h:116
@ EAC3Audio
A/53 Part 3:2009 6.7.3.
Definition: mpegtables.h:129
@ MPEG2AACAudio
ISO 13818-7 Audio w/ADTS syntax.
Definition: mpegtables.h:126
@ AC3Audio
A/53 Part 3:2009 6.7.1.
Definition: mpegtables.h:128
@ H265Video
ISO 23008-2 & ITU H.265 (aka HEVC, Ultra HD)
Definition: mpegtables.h:119
@ MPEG2Audio
ISO 13818-3.
Definition: mpegtables.h:125
@ VC1Video
SMPTE 421M video codec (aka VC1) in Blu-Ray.
Definition: mpegtables.h:121
@ MPEG4Video
ISO 14492-2 (aka MPEG-4)
Definition: mpegtables.h:117
@ PrivSec
ISO 13818-1 private tables & ITU H.222.0.
Definition: mpegtables.h:146
@ MPEG2AudioAmd1
ISO 13818-3/AMD-1 Audio using LATM syntax.
Definition: mpegtables.h:127
@ MPEG1Audio
ISO 11172-3.
Definition: mpegtables.h:124
@ MPEG1Video
ISO 11172-2 (aka MPEG-1)
Definition: mpegtables.h:115
@ OpenCableVideo
Always MPEG-2??
Definition: mpegtables.h:120
@ H264Video
ISO 14492-10 & ITU H.264 (aka MPEG-4-AVC)
Definition: mpegtables.h:118
static bool IsAudio(uint type)
Returns true iff audio is MPEG1/2, AAC, AC3 or DTS audio stream.
Definition: mpegtables.h:179
static bool IsVideo(uint type)
Returns true iff video is an MPEG1/2/3, H264 or open cable video stream.
Definition: mpegtables.h:168
unsigned int ContinuityCounter(void) const
Definition: tspacket.h:109
unsigned int PID(void) const
Definition: tspacket.h:93
bool HasPayload(void) const
Definition: tspacket.h:116
bool PayloadStart(void) const
Definition: tspacket.h:89
void SetContinuityCounter(unsigned int cc)
Definition: tspacket.h:170
const unsigned char * data(void) const
Definition: tspacket.h:174
Used to access the data of a Transport Stream packet.
Definition: tspacket.h:208
unsigned int AFCOffset(void) const
Definition: tspacket.h:249
static constexpr unsigned int kSize
Definition: tspacket.h:261
This is the coordinating class of the Recorder Subsystem.
Definition: tv_rec.h:142
unsigned int uint
Definition: compat.h:60
static int64_t extract_timestamp(const uint8_t *bufptr, int bytes_left, int pts_or_dts)
#define LOC
DTVRecorder – base class for Digital Televison recorders Copyright 2003-2004 by Brandon Beattie,...
Definition: dtvrecorder.cpp:40
static const std::array< const MythAVRational, 16 > frameRateMap
static QDateTime ts_to_qdatetime(uint64_t pts, uint64_t pts_first, const QDateTime &pts_first_dt)
@ kExtractDTS
@ kExtractPTS
@ kPIDPriorityLow
std::chrono::milliseconds millisecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:79
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
void SendMythSystemRecEvent(const QString &msg, const RecordingInfo *pginfo)
bool start_code_is_valid(uint32_t start_code)
Test whether a start code found by find_start_code() is valid.
Definition: bytereader.h:62
MTV_PUBLIC const uint8_t * find_start_code_truncated(const uint8_t *p, const uint8_t *end, uint32_t *start_code)
By preserving the start_code value between subsequent calls, the caller can detect start codes across...
Definition: bytereader.cpp:79
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
std::chrono::duration< CHRONO_TYPE, std::ratio< 1, 90000 > > pts
Definition: mythchrono.h:44
@ MARK_GOP_BYFRAME
Definition: programtypes.h:63
@ MARK_DURATION_MS
Definition: programtypes.h:73
@ formatMPEG2_TS
Definition: recordingfile.h:15
@ kSingleRecord
SCAN_t
Definition: scantype.h:6
@ INTERLACED
@ UNKNOWN_SCAN