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
622 {
624 }
625 else
626 {
627 /* Found a frame that is not a keyframe, and we want to
628 * start on a keyframe */
629 m_payloadBuffer.clear();
630 }
631 }
632
633 if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
634 {
635 m_videoAspect = aspectRatio;
637 }
638
639 if (height && width && (height != m_videoHeight || m_videoWidth != width))
640 {
641 m_videoHeight = height;
642 m_videoWidth = width;
644 }
645
646 if (frameRate.isNonzero() && frameRate != m_frameRate)
647 {
648 m_frameRate = frameRate;
649 LOG(VB_RECORD, LOG_INFO, LOC +
650 QString("FindMPEG2Keyframes: frame rate = %1")
651 .arg(frameRate.toDouble() * 1000));
652 FrameRateChange(frameRate.toDouble() * 1000, m_framesWrittenCount);
653 }
654
655 return m_firstKeyframe >= 0;
656}
657
658void DTVRecorder::HandleTimestamps(int stream_id, int64_t pts, int64_t dts)
659{
660 if (pts < 0)
661 {
662 m_tsLast[stream_id] = -1;
663 return;
664 }
665
666 if ((dts < 0) && !m_usePts)
667 {
668 m_tsLast[stream_id] = -1;
669 m_usePts = true;
670 LOG(VB_RECORD, LOG_DEBUG,
671 "Switching from dts tracking to pts tracking." +
672 QString("TS count is %1").arg(m_tsCount[stream_id]));
673 }
674
675 int64_t ts = dts;
676 int64_t gap_threshold = 90000; // 1 second
677 if (m_usePts)
678 {
679 ts = dts;
680 gap_threshold = 2*90000LL; // two seconds, compensate for GOP ordering
681 }
682
683 if (m_musicChoice)
684 gap_threshold = 8*90000LL; // music choice uses frames every 6 seconds
685
686 if (m_tsLast[stream_id] >= 0)
687 {
688 int64_t diff = ts - m_tsLast[stream_id];
689
690 // time jumped back more then 10 seconds, handle it as 33bit overflow
691 if (diff < (10 * -90000LL))
692 // MAX_PTS is 33bits all 1
693 diff += 0x1ffffffffLL;
694
695 // FIXME why do we handle negative gaps (aka overlap) like a gap?
696 if (diff < 0)
697 diff = -diff;
698
699 if (diff > gap_threshold && m_firstKeyframe >= 0)
700 {
701 QMutexLocker locker(&m_statisticsLock);
702
703 m_recordingGaps.push_back(
706 m_tsLast[stream_id], m_tsFirst[stream_id],
707 m_tsFirstDt[stream_id]),
709 ts, m_tsFirst[stream_id], m_tsFirstDt[stream_id])));
710 LOG(VB_RECORD, LOG_DEBUG, LOC + QString("Inserted gap %1 dur %2")
711 .arg(m_recordingGaps.back().toString()).arg(diff/90000.0));
712
714 {
716 if (recq.IsDamaged())
717 {
718 LOG(VB_GENERAL, LOG_INFO, LOC +
719 QString("HandleTimestamps: too much damage, "
720 "setting status to %1")
722 SetRecordingStatus(RecStatus::Failing, __FILE__, __LINE__);
723 }
724 }
725 }
726 }
727
728 m_tsLast[stream_id] = ts;
729
730 if (m_tsCount[stream_id] < 30)
731 {
732 if (!m_tsCount[stream_id] || (ts < m_tsFirst[stream_id]))
733 {
734 m_tsFirst[stream_id] = ts;
735 m_tsFirstDt[stream_id] = MythDate::current();
736 }
737 }
738
739 m_tsCount[stream_id]++;
740}
741
743{
748 {
750 m_tdTickCount = 0;
752 }
755 {
756 // not 1000 since m_tdTickCount needs to be divided by 2 to get an equivalent frame count
757 m_totalDuration = m_tdBase + ((int64_t) 500 * m_tdTickCount *
759 }
760
761 if (m_framesWrittenCount < 2000 || m_framesWrittenCount % 1000 == 0)
762 {
763 LOG(VB_RECORD, LOG_DEBUG,
764 QString("count=%1 m_frameRate=%2 tick_frameRate=%3 "
765 "tick_cnt=%4 tick_base=%5 _total_dur=%6")
767 .arg(m_frameRate.toString(),
769 .arg(m_tdTickCount)
770 .arg(m_tdBase)
771 .arg(m_totalDuration));
772 }
773}
774
776{
777 bool hasKeyFrame = false;
778 if (!m_ringBuffer || (GetStreamData()->VideoPIDSingleProgram() <= 0x1fff))
779 return hasKeyFrame;
780
781 static constexpr uint64_t kMsecPerDay { 24ULL * 60 * 60 * 1000 };
782 const double frame_interval = (1000.0 / m_videoFrameRate);
783 uint64_t elapsed = 0;
784 if (m_audioTimer.isValid())
785 elapsed = m_audioTimer.elapsed();
786 auto expected_frame = (uint64_t) ((double)elapsed / frame_interval);
787
788 while (m_framesSeenCount > expected_frame + 10000)
789 expected_frame += (uint64_t) ((double)kMsecPerDay / frame_interval);
790
791 if (!m_framesSeenCount || (m_framesSeenCount < expected_frame))
792 {
794 m_audioTimer.start();
795
796 m_bufferPackets = false;
798
799 if (1 == (m_framesSeenCount & 0x7))
800 {
803 hasKeyFrame = true;
804 }
805
808 }
809
810 return hasKeyFrame;
811}
812
816{
817 if (!m_ringBuffer || (GetStreamData()->VideoPIDSingleProgram() <= 0x1fff))
818 return true;
819
821 return true;
822
823 LOG(VB_RECORD, LOG_INFO, LOC + "DSMCC - FindOtherKeyframes() - "
824 "generating initial key-frame");
825
829
831
833
834 return true;
835}
836
842{
843 if (!m_ringBuffer)
844 return;
845
846 // Perform ringbuffer switch if needed.
848
849 uint64_t frameNum = m_framesWrittenCount;
850 if (m_firstKeyframe < 0)
851 {
852 m_firstKeyframe = frameNum;
853 SendMythSystemRecEvent("REC_STARTED_WRITING", m_curRecording);
854 }
855
856 // Add key frame to position map
857 m_positionMapLock.lock();
858 if (!m_positionMap.contains(frameNum))
859 {
860 int64_t startpos = m_ringBuffer->GetWritePosition() + extra;
861
862 // Don't put negative offsets into the database, they get munged into
863 // MAX_INT64 - offset, which is an exceedingly large number, and
864 // certainly not valid.
865 if (startpos >= 0)
866 {
867 m_positionMapDelta[frameNum] = startpos;
868 m_positionMap[frameNum] = startpos;
869 m_durationMap[frameNum] = llround(m_totalDuration);
870 m_durationMapDelta[frameNum] = llround(m_totalDuration);
871 }
872 }
873 m_positionMapLock.unlock();
874}
875
882{
883 if (!tspacket->HasPayload()) // no payload to scan
884 return m_firstKeyframe >= 0;
885
886 if (!m_ringBuffer)
887 {
888 LOG(VB_GENERAL, LOG_ERR, LOC + "FindH2645Keyframes: No ringbuffer");
889 return m_firstKeyframe >= 0;
890 }
891
892 if (!m_h2645Parser)
893 {
894 LOG(VB_GENERAL, LOG_ERR, LOC + "FindH2645Keyframes: m_h2645Parser not present");
895 return m_firstKeyframe >= 0;
896 }
897
898 const bool payloadStart = tspacket->PayloadStart();
899 if (payloadStart)
900 {
901 // reset PES sync state
902 m_pesSynced = false;
903 m_startCode = 0xffffffff;
904 }
905
906 uint aspectRatio = 0;
907 uint height = 0;
908 uint width = 0;
909 MythAVRational frameRate {0};
911
912 bool hasFrame = false;
913 bool hasKeyFrame = false;
914
915 // scan for PES packets and H.264 NAL units
916 uint i = tspacket->AFCOffset();
917 for (; i < TSPacket::kSize; ++i)
918 {
919 // special handling required when a new PES packet begins
920 if (payloadStart && !m_pesSynced)
921 {
922 // bounds check
923 if (i + 2 >= TSPacket::kSize)
924 {
925 LOG(VB_GENERAL, LOG_ERR, LOC +
926 "PES packet start code may overflow to next TS packet, "
927 "aborting keyframe search");
928 break;
929 }
930
931 // must find the PES start code
932 if (tspacket->data()[i ] != 0x00 ||
933 tspacket->data()[i+1] != 0x00 ||
934 tspacket->data()[i+2] != 0x01)
935 {
936 if (!m_pesTimer.isRunning() || m_pesTimer.elapsed() > 20000ms)
937 {
939 LOG(VB_GENERAL, LOG_ERR, LOC +
940 "PES start code not found in TS packet with PUSI set");
941 }
942 break;
943 }
944 i += 3;
945
947
948 // bounds check
949 if (i + 5 >= TSPacket::kSize)
950 {
951 LOG(VB_GENERAL, LOG_ERR, LOC +
952 "PES packet headers overflow to next TS packet, "
953 "aborting keyframe search");
954 break;
955 }
956
957 // now we need to compute where the PES payload begins
958 // skip past the stream_id (+1)
959 // the next two bytes are the PES packet length (+2)
960 // after that, one byte of PES packet control bits (+1)
961 // after that, one byte of PES header flags bits (+1)
962 // and finally, one byte for the PES header length
963 const unsigned char pes_header_length = tspacket->data()[i + 5];
964
965 // bounds check
966 if ((i + 6 + pes_header_length) >= TSPacket::kSize)
967 {
968 LOG(VB_GENERAL, LOG_ERR, LOC +
969 "PES packet headers overflow to next TS packet, "
970 "aborting keyframe search");
971 break;
972 }
973
974 // we now know where the PES payload is
975 // normally, we should have used 6, but use 5 because the for
976 // loop will bump i
977 i += 5 + pes_header_length;
978 m_pesSynced = true;
979
980#if 0
981 LOG(VB_RECORD, LOG_DEBUG, LOC + "PES synced");
982#endif
983 continue;
984 }
985
986 if (!m_pesSynced)
987 break;
988
989 // scan the NAL units
990 uint32_t bytes_used = m_h2645Parser->addBytes
991 (tspacket->data() + i, TSPacket::kSize - i,
993 i += (bytes_used - 1);
994
996 {
999 {
1000 hasKeyFrame = m_h2645Parser->onKeyFrameStart();
1001 hasFrame = true;
1002 m_seenSps |= hasKeyFrame;
1003
1004 width = m_h2645Parser->pictureWidth();
1005 height = m_h2645Parser->pictureHeight();
1006 aspectRatio = m_h2645Parser->aspectRatio();
1007 scantype = m_h2645Parser->GetScanType();
1008 frameRate = m_h2645Parser->getFrameRate();
1009 }
1010 }
1011 } // for (; i < TSPacket::kSize; ++i)
1012
1013 // If it has been more than 511 frames since the last keyframe,
1014 // pretend we have one.
1015 if (hasFrame && !hasKeyFrame &&
1017 {
1018 hasKeyFrame = true;
1019 LOG(VB_RECORD, LOG_WARNING, LOC +
1020 QString("FindH2645Keyframes: %1 frames without a keyframe.")
1022 }
1023
1024 // m_bufferPackets will only be true if a payload start has been seen
1025 if (hasKeyFrame && (m_bufferPackets || m_firstKeyframe >= 0))
1026 {
1027 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
1028 ("Keyframe @ %1 + %2 = %3 AU %4")
1030 .arg(m_payloadBuffer.size())
1033
1036 }
1037
1038 if (hasFrame)
1039 {
1040 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
1041 ("Frame @ %1 + %2 = %3 AU %4")
1043 .arg(m_payloadBuffer.size())
1046
1047 m_bufferPackets = false; // We now know if this is a keyframe
1050 {
1052 }
1053 else
1054 {
1055 /* Found a frame that is not a keyframe, and we want to
1056 * start on a keyframe */
1057 m_payloadBuffer.clear();
1058 }
1059 }
1060
1061 if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
1062 {
1063 m_videoAspect = aspectRatio;
1065 }
1066
1067 if (height && width && (height != m_videoHeight || m_videoWidth != width))
1068 {
1069 m_videoHeight = height;
1070 m_videoWidth = width;
1071 ResolutionChange(width, height, m_framesWrittenCount);
1072 }
1073
1074 if (frameRate.isNonzero() && frameRate != m_frameRate)
1075 {
1076 LOG(VB_RECORD, LOG_INFO, LOC +
1077 QString("FindH2645Keyframes: timescale: %1, tick: %2, framerate: %3")
1078 .arg( m_h2645Parser->GetTimeScale() )
1079 .arg( m_h2645Parser->GetUnitsInTick() )
1080 .arg( frameRate.toDouble() * 1000 ) );
1081 m_frameRate = frameRate;
1082 FrameRateChange(frameRate.toDouble() * 1000, m_framesWrittenCount);
1083 }
1084
1085 if (scantype != SCAN_t::UNKNOWN_SCAN && scantype != m_scanType)
1086 {
1087 LOG(VB_RECORD, LOG_INFO, LOC +
1088 QString("FindH2645Keyframes: scan type: %1")
1089 .arg(scantype == SCAN_t::INTERLACED ?
1090 "Interlaced" : "Progressive"));
1091 m_scanType = scantype;
1093 }
1094
1095 return m_seenSps;
1096}
1097
1103{
1104 // Perform ringbuffer switch if needed.
1106
1107 uint64_t startpos = 0;
1108 uint64_t frameNum = m_framesWrittenCount;
1109
1110 if (m_firstKeyframe < 0)
1111 {
1112 m_firstKeyframe = frameNum;
1113 startpos = 0;
1114 SendMythSystemRecEvent("REC_STARTED_WRITING", m_curRecording);
1115 }
1116 else
1117 {
1119 }
1120
1121 // Add key frame to position map
1122 m_positionMapLock.lock();
1123 if (!m_positionMap.contains(frameNum))
1124 {
1125 m_positionMapDelta[frameNum] = startpos;
1126 m_positionMap[frameNum] = startpos;
1127 m_durationMap[frameNum] = llround(m_totalDuration);
1128 m_durationMapDelta[frameNum] = llround(m_totalDuration);
1129 }
1130 m_positionMapLock.unlock();
1131}
1132
1133void DTVRecorder::FindPSKeyFrames(const uint8_t *buffer, uint len)
1134{
1135 const uint maxKFD = kMaxKeyFrameDistance;
1136
1137 const uint8_t *bufstart = buffer;
1138 const uint8_t *bufptr = buffer;
1139 const uint8_t *bufend = buffer + len;
1140
1141 uint aspectRatio = 0;
1142 uint height = 0;
1143 uint width = 0;
1144 MythAVRational frameRate {0};
1145
1147 while (bufptr + skip < bufend)
1148 {
1149 bool hasFrame = false;
1150 bool hasKeyFrame = false;
1151
1152 const uint8_t *tmp = bufptr;
1153 bufptr = ByteReader::find_start_code_truncated(bufptr + skip, bufend, &m_startCode);
1156 m_videoBytesRemaining -= std::min(
1157 (uint)(bufptr - tmp), m_videoBytesRemaining);
1158
1160 continue;
1161
1162 // NOTE: Length may be zero for packets that only contain bytes from
1163 // video elementary streams in TS packets. 13818-1:2000 2.4.3.7
1164 int pes_packet_length = -1;
1165 if ((bufend - bufptr) >= 2)
1166 pes_packet_length = ((bufptr[0]<<8) | bufptr[1]) + 2 + 6;
1167
1168 const int stream_id = m_startCode & 0x000000ff;
1170 {
1171 if (PESStreamID::PictureStartCode == stream_id)
1172 { // pes_packet_length is meaningless
1173 pes_packet_length = -1;
1174 if (bufend-bufptr >= 4)
1175 {
1176 uint frmtypei = (bufptr[1]>>3) & 0x7;
1177 if ((1 <= frmtypei) && (frmtypei <= 5))
1178 hasFrame = true;
1179 }
1180 else
1181 {
1182 hasFrame = true;
1183 }
1184 }
1185 else if (PESStreamID::GOPStartCode == stream_id)
1186 { // pes_packet_length is meaningless
1187 pes_packet_length = -1;
1189 hasKeyFrame = true;
1190 }
1191 else if (PESStreamID::SequenceStartCode == stream_id)
1192 { // pes_packet_length is meaningless
1193 pes_packet_length = -1;
1195 hasKeyFrame |= (m_lastGopSeen + maxKFD)<m_framesSeenCount;
1196
1197 // Look for aspectRatio changes and store them in the database
1198 aspectRatio = (bufptr[3] >> 4);
1199
1200 // Get resolution
1201 height = ((bufptr[1] & 0xf) << 8) | bufptr[2];
1202 width = (bufptr[0] <<4) | (bufptr[1]>>4);
1203
1204 frameRate = frameRateMap[(bufptr[3] & 0x0000000f)];
1205 }
1206 }
1207 else if (!m_audioBytesRemaining)
1208 {
1209 if ((stream_id >= PESStreamID::MPEGVideoStreamBegin) &&
1210 (stream_id <= PESStreamID::MPEGVideoStreamEnd))
1211 { // ok-dvdinfo
1212 m_videoBytesRemaining = std::max(0, pes_packet_length);
1213 }
1214 else if ((stream_id >= PESStreamID::MPEGAudioStreamBegin) &&
1215 (stream_id <= PESStreamID::MPEGAudioStreamEnd))
1216 { // ok-dvdinfo
1217 m_audioBytesRemaining = std::max(0, pes_packet_length);
1218 }
1219 }
1220
1221 if (PESStreamID::PaddingStream == stream_id)
1222 { // ok-dvdinfo
1223 m_otherBytesRemaining = std::max(0, pes_packet_length);
1224 }
1225
1226 m_startCode = 0xffffffff; // reset start code
1227
1228 if (hasFrame && !hasKeyFrame)
1229 {
1230 // If we have seen kMaxKeyFrameDistance frames since the
1231 // last GOP or SEQ stream_id, then pretend this picture
1232 // is a keyframe. We may get artifacts but at least
1233 // we will be able to skip frames.
1234 hasKeyFrame = ((m_framesSeenCount & 0xf) == 0U);
1235 hasKeyFrame &= (m_lastGopSeen + maxKFD) < m_framesSeenCount;
1236 hasKeyFrame &= (m_lastSeqSeen + maxKFD) < m_framesSeenCount;
1237 }
1238
1239 if (hasFrame)
1240 {
1244 }
1245
1246 if (hasKeyFrame)
1247 {
1249 HandleKeyframe((int64_t)m_payloadBuffer.size() - (bufptr - bufstart));
1250 }
1251
1252 if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
1253 {
1254 m_videoAspect = aspectRatio;
1256 }
1257
1258 if (height && width &&
1259 (height != m_videoHeight || m_videoWidth != width))
1260 {
1261 m_videoHeight = height;
1262 m_videoWidth = width;
1263 ResolutionChange(width, height, m_framesWrittenCount);
1264 }
1265
1266 if (frameRate.isNonzero() && frameRate != m_frameRate)
1267 {
1268 m_frameRate = frameRate;
1269 LOG(VB_RECORD, LOG_INFO, LOC +
1270 QString("FindPSKeyFrames: frame rate = %1")
1271 .arg(frameRate.toDouble() * 1000));
1272 FrameRateChange(frameRate.toDouble() * 1000, m_framesWrittenCount);
1273 }
1274
1275 if (hasKeyFrame || hasFrame)
1276 {
1277 // We are free to write the packet, but if we have
1278 // buffered packet[s] we have to write them first...
1279 if (!m_payloadBuffer.empty())
1280 {
1281 if (m_ringBuffer)
1282 {
1284 (m_payloadBuffer).data(), m_payloadBuffer.size());
1285 }
1286 m_payloadBuffer.clear();
1287 }
1288
1289 if (m_ringBuffer)
1290 m_ringBuffer->Write(bufstart, (bufptr - bufstart));
1291
1292 bufstart = bufptr;
1293 }
1294
1296 }
1297
1298 int bytes_skipped = bufend - bufptr;
1299 if (bytes_skipped > 0)
1300 {
1301 m_audioBytesRemaining -= std::min(
1302 (uint)bytes_skipped, m_audioBytesRemaining);
1303 m_videoBytesRemaining -= std::min(
1304 (uint)bytes_skipped, m_videoBytesRemaining);
1305 m_otherBytesRemaining -= std::min(
1306 (uint)bytes_skipped, m_otherBytesRemaining);
1307 }
1308
1309 uint64_t idx = m_payloadBuffer.size();
1310 uint64_t rem = (bufend - bufstart);
1311 m_payloadBuffer.resize(idx + rem);
1312 memcpy(&m_payloadBuffer[idx], bufstart, rem);
1313#if 0
1314 LOG(VB_GENERAL, LOG_DEBUG, LOC +
1315 QString("idx: %1, rem: %2").arg(idx).arg(rem));
1316#endif
1317}
1318
1320{
1321 if (!_pat)
1322 {
1323 LOG(VB_RECORD, LOG_ERR, LOC + "SetPAT(NULL)");
1324 return;
1325 }
1326
1327 QMutexLocker change_lock(&m_pidLock);
1328
1329 int progNum = m_streamData->DesiredProgram();
1330 uint pmtpid = _pat->FindPID(progNum);
1331
1332 // If we have not found the desired program in the PAT and this happens to be
1333 // an SPTS then update the desired program to the one that is present in the PAT.
1334 if (!pmtpid)
1335 {
1336 if (_pat->ProgramCount() == 1)
1337 {
1338 int oldProgNum = progNum;
1339 progNum = _pat->ProgramNumber(0);
1340 LOG(VB_GENERAL, LOG_INFO, LOC +
1341 QString("Update desired program found in SPTS PAT from %1 to %2")
1342 .arg(oldProgNum).arg(progNum));
1343 GetStreamData()->SetDesiredProgram(progNum);
1344 pmtpid = _pat->FindPID(progNum);
1345 }
1346 }
1347 if (!pmtpid)
1348 {
1349 LOG(VB_RECORD, LOG_ERR, LOC +
1350 QString("SetPAT(): Ignoring PAT not containing our desired "
1351 "program (%1)...").arg(progNum));
1352 return;
1353 }
1354
1355 LOG(VB_RECORD, LOG_INFO, LOC + QString("SetPAT(%1 on pid 0x%2)")
1356 .arg(progNum).arg(pmtpid,0,16));
1357
1360 delete oldpat;
1361
1362 // Listen for the other PMTs for faster channel switching
1363 for (uint i = 0; m_inputPat && (i < m_inputPat->ProgramCount()); ++i)
1364 {
1365 uint pmt_pid = m_inputPat->ProgramPID(i);
1366 if (!m_streamData->IsListeningPID(pmt_pid))
1368 }
1369}
1370
1372{
1373 QMutexLocker change_lock(&m_pidLock);
1374
1375 LOG(VB_RECORD, LOG_INFO, LOC + QString("SetPMT(%1, %2)").arg(progNum)
1376 .arg(_pmt == nullptr ? "NULL" : "valid"));
1377
1378
1379 if ((int)progNum == m_streamData->DesiredProgram())
1380 {
1381 LOG(VB_RECORD, LOG_INFO, LOC + QString("SetPMT(%1)").arg(progNum));
1382 ProgramMapTable *oldpmt = m_inputPmt;
1383 m_inputPmt = new ProgramMapTable(*_pmt);
1384
1385 QString sistandard = GetSIStandard();
1386
1387 bool has_no_av = true;
1388 for (uint i = 0; i < m_inputPmt->StreamCount() && has_no_av; ++i)
1389 {
1390 has_no_av &= !m_inputPmt->IsVideo(i, sistandard);
1391 has_no_av &= !m_inputPmt->IsAudio(i, sistandard);
1392 }
1393 m_hasNoAV = has_no_av;
1394
1396 delete oldpmt;
1397 }
1398}
1399
1401 bool insert)
1402{
1403 if (!pat)
1404 {
1405 LOG(VB_RECORD, LOG_ERR, LOC + "HandleSingleProgramPAT(NULL)");
1406 return;
1407 }
1408
1409 if (!m_ringBuffer)
1410 return;
1411
1412 uint next_cc = (pat->tsheader()->ContinuityCounter()+1)&0xf;
1413 pat->tsheader()->SetContinuityCounter(next_cc);
1414 pat->GetAsTSPackets(m_scratch, next_cc);
1415
1416 for (const auto & tspacket : m_scratch)
1417 DTVRecorder::BufferedWrite(tspacket, insert);
1418}
1419
1421{
1422 if (!pmt)
1423 {
1424 LOG(VB_RECORD, LOG_ERR, LOC + "HandleSingleProgramPMT(NULL)");
1425 return;
1426 }
1427
1428 // We only want to do these checks once per recording
1429 bool seenVideo = (m_primaryVideoCodec != AV_CODEC_ID_NONE);
1430 bool seenAudio = (m_primaryAudioCodec != AV_CODEC_ID_NONE);
1431 uint bestAudioCodec = 0;
1432 // collect stream types for H.264 (MPEG-4 AVC) keyframe detection
1433 for (uint i = 0; i < pmt->StreamCount(); ++i)
1434 {
1435 // We only care about the first identifiable video stream
1436 if (!seenVideo && (m_primaryVideoCodec == AV_CODEC_ID_NONE) &&
1438 {
1439 seenVideo = true; // Ignore other video streams
1440 switch (pmt->StreamType(i))
1441 {
1443 m_primaryVideoCodec = AV_CODEC_ID_MPEG1VIDEO;
1444 break;
1446 m_primaryVideoCodec = AV_CODEC_ID_MPEG2VIDEO;
1447 break;
1449 m_primaryVideoCodec = AV_CODEC_ID_MPEG4;
1450 break;
1452 m_primaryVideoCodec = AV_CODEC_ID_H264;
1453 if (dynamic_cast<AVCParser *>(m_h2645Parser) == nullptr)
1454 {
1455 delete m_h2645Parser;
1456 m_h2645Parser = nullptr;
1457 }
1458 if (m_h2645Parser == nullptr)
1459 {
1460 auto * avcParser = new AVCParser;
1461 if (avcParser != nullptr)
1462 {
1463 m_h2645Parser = reinterpret_cast<H2645Parser *>
1464 (avcParser);
1465 avcParser->use_I_forKeyframes(m_useIForKeyframe);
1466 }
1467 }
1468 break;
1470 LOG(VB_GENERAL, LOG_INFO, LOC + "HEVC detected");
1471 m_primaryVideoCodec = AV_CODEC_ID_H265;
1472 if (dynamic_cast<HEVCParser *>(m_h2645Parser) == nullptr)
1473 {
1474 delete m_h2645Parser;
1475 m_h2645Parser = nullptr;
1476 }
1477 if (m_h2645Parser == nullptr)
1478 {
1479 m_h2645Parser = reinterpret_cast<H2645Parser *>
1480 (new HEVCParser);
1481 }
1482 break;
1484 m_primaryVideoCodec = AV_CODEC_ID_MPEG2VIDEO; // TODO Will it always be MPEG2?
1485 break;
1486 case StreamID::VC1Video:
1487 m_primaryVideoCodec = AV_CODEC_ID_VC1;
1488 break;
1489 default:
1490 break;
1491 }
1492
1493 if (m_primaryVideoCodec != AV_CODEC_ID_NONE)
1495 }
1496
1497 // We want the 'best' identifiable audio stream, where 'best' is
1498 // subjective and no-one will likely agree.
1499 // For now it's the 'best' codec, assuming mpeg stream types range
1500 // from worst to best, which it does
1501 if (!seenAudio && StreamID::IsAudio(pmt->StreamType(i)) &&
1502 pmt->StreamType(i) > bestAudioCodec)
1503 {
1504 bestAudioCodec = pmt->StreamType(i);
1505 switch (pmt->StreamType(i))
1506 {
1507 case StreamID::MPEG1Audio: // MPEG-1 Layer 2 (MP2)
1508 case StreamID::MPEG2Audio: // MPEG-2 Part 3 (MP2 Multichannel)
1509 m_primaryAudioCodec = AV_CODEC_ID_MP2;
1510 break;
1512 m_primaryAudioCodec = AV_CODEC_ID_AAC;
1513 break;
1515 m_primaryAudioCodec = AV_CODEC_ID_AAC_LATM;
1516 break;
1517 case StreamID::AC3Audio:
1518 m_primaryAudioCodec = AV_CODEC_ID_AC3;
1519 break;
1521 m_primaryAudioCodec = AV_CODEC_ID_EAC3;
1522 break;
1523 case StreamID::DTSAudio:
1524 m_primaryAudioCodec = AV_CODEC_ID_DTS;
1525 break;
1526 default:
1527 break;
1528 }
1529
1530 if (m_primaryAudioCodec != AV_CODEC_ID_NONE)
1532 }
1533
1534// LOG(VB_GENERAL, LOG_DEBUG, QString("Recording(%1): Stream #%2: %3 ")
1535// .arg(m_curRecording ? QString::number(m_curRecording->GetRecordingID()) : "")
1536// .arg(i)
1537// .arg(StreamID::GetDescription(pmt->StreamType(i))));
1538 m_streamId[pmt->StreamPID(i)] = pmt->StreamType(i);
1539 }
1540
1541 // If the PCRPID is valid and the PCR is not contained
1542 // in another stream, make sure the PCR stream is not
1543 // discarded (use PrivSec type as dummy 'valid' value)
1544 if (pmt->PCRPID() != 0x1fff && pmt->FindPID(pmt->PCRPID()) == -1)
1546
1547 if (!m_ringBuffer)
1548 return;
1549
1550 uint next_cc = (pmt->tsheader()->ContinuityCounter()+1)&0xf;
1551 pmt->tsheader()->SetContinuityCounter(next_cc);
1552 pmt->GetAsTSPackets(m_scratch, next_cc);
1553
1554 for (const auto & tspacket : m_scratch)
1555 DTVRecorder::BufferedWrite(tspacket, insert);
1556}
1557
1559{
1560 const uint pid = tspacket.PID();
1561
1562 if (pid != 0x1fff)
1563 m_packetCount.fetchAndAddAcquire(1);
1564
1565 // Check continuity counter
1566 uint old_cnt = m_continuityCounter[pid];
1567 if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter()))
1568 {
1569 int v = m_continuityErrorCount.fetchAndAddRelaxed(1) + 1;
1570 double erate = v * 100.0 / m_packetCount.fetchAndAddRelaxed(0);
1571 LOG(VB_RECORD, LOG_WARNING, LOC +
1572 QString("PID 0x%1 discontinuity detected ((%2+1)%16!=%3) %4%")
1573 .arg(pid,0,16).arg(old_cnt,2)
1574 .arg(tspacket.ContinuityCounter(),2)
1575 .arg(erate));
1576 }
1577
1578 // Only create fake keyframe[s] if there are no audio/video streams
1579 if (m_inputPmt && m_hasNoAV)
1580 {
1581 FindOtherKeyframes(&tspacket);
1582 m_bufferPackets = false;
1583 }
1584 else if (m_recordMptsOnly)
1585 {
1586 /* When recording the full, unfiltered, MPTS, trigger a write
1587 * every 0.5 seconds. Since the packets are unfiltered and
1588 * unprocessed we cannot wait for a keyframe to trigger the
1589 * writes. */
1590
1591 if (m_framesSeenCount++ == 0)
1593
1594 if (m_recordMptsTimer.elapsed() > 0.5s)
1595 {
1600 }
1601 }
1602 else if (m_streamId[pid] == 0)
1603 {
1604 // Ignore this packet if the PID should be stripped
1605 return true;
1606 }
1607 else
1608 {
1609 // There are audio/video streams. Only write the packet
1610 // if audio/video key-frames have been found
1612 return true;
1613 }
1614
1615 BufferedWrite(tspacket);
1616
1617 return true;
1618}
1619
1621{
1622 if (!m_ringBuffer)
1623 return true;
1624
1625 uint streamType = m_streamId[tspacket.PID()];
1626
1627 if (tspacket.HasPayload() && tspacket.PayloadStart())
1628 {
1629 if (m_bufferPackets && m_firstKeyframe >= 0 && !m_payloadBuffer.empty())
1630 {
1631 // Flush the buffer
1632 if (m_ringBuffer)
1634 m_payloadBuffer.clear();
1635 }
1636
1637 // buffer packets until we know if this is a keyframe
1638 m_bufferPackets = true;
1639 }
1640
1641 // Check for keyframes and count frames
1642 if (streamType == StreamID::H264Video ||
1643 streamType == StreamID::H265Video)
1644 FindH2645Keyframes(&tspacket);
1645 else if (streamType != 0)
1646 FindMPEG2Keyframes(&tspacket);
1647 else
1648 LOG(VB_RECORD, LOG_ERR, LOC +
1649 "ProcessVideoTSPacket: unknown stream type!");
1650
1651 return ProcessAVTSPacket(tspacket);
1652}
1653
1655{
1656 if (!m_ringBuffer)
1657 return true;
1658
1659 if (tspacket.HasPayload() && tspacket.PayloadStart())
1660 {
1661 if (m_bufferPackets && m_firstKeyframe >= 0 && !m_payloadBuffer.empty())
1662 {
1663 // Flush the buffer
1664 if (m_ringBuffer)
1666 m_payloadBuffer.clear();
1667 }
1668
1669 // buffer packets until we know if this is a keyframe
1670 m_bufferPackets = true;
1671 }
1672
1673 FindAudioKeyframes(&tspacket);
1674 return ProcessAVTSPacket(tspacket);
1675}
1676
1679{
1680 // Sync recording start to first keyframe
1682 {
1683 if (m_bufferPackets)
1684 BufferedWrite(tspacket);
1685 return true;
1686 }
1687
1688 const uint pid = tspacket.PID();
1689
1690 if (pid != 0x1fff)
1691 m_packetCount.fetchAndAddAcquire(1);
1692
1693 // Check continuity counter
1694 uint old_cnt = m_continuityCounter[pid];
1695 if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter()))
1696 {
1697 int v = m_continuityErrorCount.fetchAndAddRelaxed(1) + 1;
1698 double erate = v * 100.0 / m_packetCount.fetchAndAddRelaxed(0);
1699 LOG(VB_RECORD, LOG_WARNING, LOC +
1700 QString("A/V PID 0x%1 discontinuity detected ((%2+1)%16!=%3) %4%")
1701 .arg(pid,0,16).arg(old_cnt).arg(tspacket.ContinuityCounter())
1702 .arg(erate,5,'f',2));
1703 }
1704
1705 if (!(m_pidStatus[pid] & kPayloadStartSeen))
1706 {
1708 LOG(VB_RECORD, LOG_INFO, LOC +
1709 QString("PID 0x%1 Found Payload Start").arg(pid,0,16));
1710 }
1711
1712 BufferedWrite(tspacket);
1713
1714 return true;
1715}
1716
1718{
1720 recq->AddTSStatistics(
1721 m_continuityErrorCount.fetchAndAddRelaxed(0),
1722 m_packetCount.fetchAndAddRelaxed(0));
1723 return recq;
1724}
1725
1726/* 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:134
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