MythTV master
dtvrecorder.cpp
Go to the documentation of this file.
1
25
26#include "bytereader.h"
27#include "dtvrecorder.h"
28#include "io/mythmediabuffer.h"
29#include "mpeg/AVCParser.h"
30#include "mpeg/HEVCParser.h"
31#include "mpeg/atscstreamdata.h"
32#include "mpeg/dvbstreamdata.h"
33#include "mpeg/mpegstreamdata.h"
34#include "mpeg/mpegtables.h"
35#include "mythsystemevent.h"
36#include "tv_rec.h"
37
38#define LOC ((m_tvrec) ? \
39 QString("DTVRec[%1]: ").arg(m_tvrec->GetInputId()) : \
40 QString("DTVRec(0x%1): ").arg(intptr_t(this),0,16))
41
43
53 RecorderBase(rec),
54 m_h2645Parser(reinterpret_cast<H2645Parser *>(new AVCParser))
55{
57 m_payloadBuffer.reserve(TSPacket::kSize * (50_UZ + 1));
58
60
62 gCoreContext->GetNumSetting("MinimumRecordingQuality", 95);
63
65}
66
68{
70
72
73 if (m_inputPat)
74 {
75 delete m_inputPat;
76 m_inputPat = nullptr;
77 }
78
79 if (m_inputPmt)
80 {
81 delete m_inputPmt;
82 m_inputPmt = nullptr;
83 }
84
85 if (m_h2645Parser)
86 {
87 delete m_h2645Parser;
88 m_h2645Parser = nullptr;
89 }
90}
91
92void DTVRecorder::SetOption(const QString &name, const QString &value)
93{
94 if (name == "recordingtype")
95 m_recordingType = value;
96 else
97 RecorderBase::SetOption(name, value);
98}
99
103void DTVRecorder::SetOption(const QString &name, int value)
104{
105 if (name == "wait_for_seqstart")
106 m_waitForKeyframeOption = (value == 1);
107 else if (name == "recordmpts")
108 m_recordMpts = (value != 0);
109 else
110 RecorderBase::SetOption(name, value);
111}
112
114 const QString &videodev,
115 const QString& /*audiodev*/, const QString& /*vbidev*/)
116{
117 SetOption("videodevice", videodev);
118 DTVRecorder::SetOption("tvformat", gCoreContext->GetSetting("TVFormat"));
119 SetStrOption(profile, "recordingtype");
120 SetIntOption(profile, "recordmpts");
121}
122
128{
129 if (m_ringBuffer)
131
132 if (m_curRecording)
133 {
136 }
137
139}
140
142{
143 LOG(VB_RECORD, LOG_INFO, LOC + "ResetForNewFile(void)");
144 QMutexLocker locker(&m_positionMapLock);
145
146 // m_seen_psp and m_h2645_parser should
147 // not be reset here. This will only be called just as
148 // we're seeing the first packet of a new keyframe for
149 // writing to the new file and anything that makes the
150 // recorder think we're waiting on another keyframe will
151 // send significant amounts of good data to /dev/null.
152 // -- Daniel Kristjansson 2011-02-26
153
154 m_startCode = 0xffffffff;
155 m_firstKeyframe = -1;
158 m_lastGopSeen = 0;
159 m_lastSeqSeen = 0;
163 //_recording
164 m_error = QString();
165
167 m_repeatPict = 0;
168
169 //m_pes_synced
170 //m_seen_sps
171 m_positionMap.clear();
172 m_positionMapDelta.clear();
173 m_durationMap.clear();
174 m_durationMapDelta.clear();
175
176 m_primaryVideoCodec = AV_CODEC_ID_NONE;
177 m_primaryAudioCodec = AV_CODEC_ID_NONE;
178
179 m_continuityCounter.fill(0xff);
180
181 locker.unlock();
183}
184
186{
188
189 m_tsCount.fill(0);
190 m_tsLast.fill(-1LL);
191 m_tsFirst.fill(-1LL);
192 //m_tsFirst_dt -- doesn't need to be cleared only used if m_tsFirst>=0
193 m_packetCount.fetchAndStoreRelaxed(0);
194 m_continuityErrorCount.fetchAndStoreRelaxed(0);
197 m_totalDuration = 0;
198 m_tdBase = 0;
199 m_tdTickCount = 0;
201
202}
203
204// documented in recorderbase.h
206{
207 LOG(VB_RECORD, LOG_INFO, LOC + "Reset(void)");
209
210 m_startCode = 0xffffffff;
211
212 if (m_curRecording)
213 {
216 }
217}
218
220{
221 if (data == m_streamData)
222 return;
223
224 MPEGStreamData *old_data = m_streamData;
225 m_streamData = data;
226 delete old_data;
227
228 if (m_streamData)
230}
231
233{
234 if (m_streamData == nullptr)
235 return;
236
239
240 auto *dvb = dynamic_cast<DVBStreamData*>(m_streamData);
241 if (dvb)
242 dvb->AddDVBMainListener(this);
243
244 auto *atsc = dynamic_cast<ATSCStreamData*>(m_streamData);
245
246 if (atsc && atsc->DesiredMinorChannel())
247 {
248 atsc->SetDesiredChannel(atsc->DesiredMajorChannel(),
249 atsc->DesiredMinorChannel());
250 }
251 // clang-tidy assumes that if the result of dynamic_cast is
252 // nullptr that the thing being casted must also be a nullptr.
253 // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
254 else if (m_streamData->DesiredProgram() >= 0)
255 {
257 }
258}
259
260void DTVRecorder::BufferedWrite(const TSPacket &tspacket, bool insert)
261{
262 if (!insert) // PAT/PMT may need inserted in front of any buffered data
263 {
264 // delay until first GOP to avoid decoder crash on res change
266 m_firstKeyframe < 0)
267 return;
268
269 if (m_curRecording && m_timeOfFirstDataIsSet.testAndSetRelaxed(0,1))
270 {
271 QMutexLocker locker(&m_statisticsLock);
275 }
276
277 int val = m_timeOfLatestDataCount.fetchAndAddRelaxed(1);
278 int thresh = m_timeOfLatestDataPacketInterval.fetchAndAddRelaxed(0);
279 if (val > thresh)
280 {
281 QMutexLocker locker(&m_statisticsLock);
282 std::chrono::milliseconds elapsed = m_timeOfLatestDataTimer.restart();
283 int interval = thresh;
284 if (elapsed > kTimeOfLatestDataIntervalTarget + 250ms)
285 {
287 .fetchAndStoreRelaxed(thresh * 4 / 5);
288 }
289 else if (elapsed + 250ms < kTimeOfLatestDataIntervalTarget)
290 {
292 .fetchAndStoreRelaxed(thresh * 9 / 8);
293 }
294
295 m_timeOfLatestDataCount.fetchAndStoreRelaxed(1);
297
298 LOG(VB_RECORD, LOG_DEBUG, LOC +
299 QString("Updating timeOfLatestData elapsed(%1) interval(%2)")
300 .arg(elapsed.count()).arg(interval));
301 }
302
303 // Do we have to buffer the packet for exact keyframe detection?
304 if (m_bufferPackets)
305 {
306 int idx = m_payloadBuffer.size();
307 m_payloadBuffer.resize(idx + TSPacket::kSize);
308 memcpy(&m_payloadBuffer[idx], tspacket.data(), TSPacket::kSize);
309 return;
310 }
311
312 // We are free to write the packet, but if we have buffered packet[s]
313 // we have to write them first...
314 if (!m_payloadBuffer.empty())
315 {
316 if (m_ringBuffer)
318 m_payloadBuffer.clear();
319 }
320 }
321
322 if (m_ringBuffer && m_ringBuffer->Write(tspacket.data(), TSPacket::kSize) < 0 &&
324 {
325 LOG(VB_GENERAL, LOG_INFO, LOC +
326 QString("BufferedWrite: Writes are failing, "
327 "setting status to %1")
329 SetRecordingStatus(RecStatus::Failing, __FILE__, __LINE__);
330 }
331}
332
333enum : std::uint8_t { kExtractPTS, kExtractDTS };
334static int64_t extract_timestamp(
335 const uint8_t *bufptr, int bytes_left, int pts_or_dts)
336{
337 if (bytes_left < 4)
338 return -1LL;
339
340 bool has_pts = (bufptr[3] & 0x80) != 0;
341 int offset = 5;
342 if (((kExtractPTS == pts_or_dts) && !has_pts) || (offset + 5 > bytes_left))
343 return -1LL;
344
345 bool has_dts = (bufptr[3] & 0x40) != 0;
346 if (kExtractDTS == pts_or_dts)
347 {
348 if (!has_dts)
349 return -1LL;
350 offset += has_pts ? 5 : 0;
351 if (offset + 5 > bytes_left)
352 return -1LL;
353 }
354
355 return ((uint64_t(bufptr[offset+0] & 0x0e) << 29) |
356 (uint64_t(bufptr[offset+1] ) << 22) |
357 (uint64_t(bufptr[offset+2] & 0xfe) << 14) |
358 (uint64_t(bufptr[offset+3] ) << 7) |
359 (uint64_t(bufptr[offset+4] & 0xfe) >> 1));
360}
361
362static QDateTime ts_to_qdatetime(
363 uint64_t pts, uint64_t pts_first, const QDateTime &pts_first_dt)
364{
365 if (pts < pts_first)
366 pts += 0x1FFFFFFFFLL;
367 const QDateTime& dt = pts_first_dt;
368 return dt.addMSecs((pts - pts_first)/90);
369}
370
371static const std::array<const MythAVRational,16> frameRateMap = {
372 MythAVRational(0), MythAVRational(24000, 1001), MythAVRational(24),
373 MythAVRational(25), MythAVRational(30000, 1001), MythAVRational(30),
374 MythAVRational(50), MythAVRational(60000, 1001), MythAVRational(60),
378};
379
407{
408 if (!tspacket->HasPayload()) // no payload to scan
409 return m_firstKeyframe >= 0;
410
411 if (!m_ringBuffer)
412 return m_firstKeyframe >= 0;
413
414 // if packet contains start of PES packet, start
415 // looking for first byte of MPEG start code (3 bytes 0 0 1)
416 // otherwise, pick up search where we left off.
417 const bool payloadStart = tspacket->PayloadStart();
418 m_startCode = (payloadStart) ? 0xffffffff : m_startCode;
419
420 // Just make these local for efficiency reasons (gcc not so smart..)
421 const uint maxKFD = kMaxKeyFrameDistance;
422 bool hasFrame = false;
423 bool hasKeyFrame = false;
424
425 uint aspectRatio = 0;
426 uint height = 0;
427 uint width = 0;
428 MythAVRational frameRate {0};
429
430 // Scan for PES header codes; specifically picture_start
431 // sequence_start (SEQ) and group_start (GOP).
432 // 00 00 01 00: picture_start_code
433 // 00 00 01 B8: group_start_code
434 // 00 00 01 B3: seq_start_code
435 // 00 00 01 B5: ext_start_code
436 // (there are others that we don't care about)
437 const uint8_t *bufptr = tspacket->data() + tspacket->AFCOffset();
438 const uint8_t *bufend = tspacket->data() + TSPacket::kSize;
439 m_repeatPict = 0;
440
441 while (bufptr < bufend)
442 {
443 bufptr = ByteReader::find_start_code_truncated(bufptr, bufend, &m_startCode);
444 int bytes_left = bufend - bufptr;
446 {
447 // At this point we have seen the start code 0 0 1
448 // the next byte will be the PES packet stream id.
449 const int stream_id = m_startCode & 0x000000ff;
450 if (PESStreamID::PictureStartCode == stream_id)
451 {
453 {
454 hasFrame = true;
455 }
456 // else deterimine hasFrame from the following picture_coding_extension()
457 }
458 else if (PESStreamID::GOPStartCode == stream_id)
459 {
461 hasKeyFrame = true;
462 }
463 else if (PESStreamID::SequenceStartCode == stream_id)
464 {
466 hasKeyFrame |= (m_lastGopSeen + maxKFD)<m_framesSeenCount;
467
468 // Look for aspectRatio changes and store them in the database
469 aspectRatio = (bufptr[3] >> 4);
470
471 // Get resolution
472 height = ((bufptr[1] & 0xf) << 8) | bufptr[2];
473 width = (bufptr[0] <<4) | (bufptr[1]>>4);
474
475 frameRate = frameRateMap[(bufptr[3] & 0x0000000f)];
476 }
477 else if (PESStreamID::MPEG2ExtensionStartCode == stream_id)
478 {
479 if (bytes_left >= 1)
480 {
481 int ext_type = (bufptr[0] >> 4);
482 switch(ext_type)
483 {
484 case 0x1: /* sequence extension */
485 if (bytes_left >= 6)
486 {
487 m_progressiveSequence = bufptr[1] & (1 << 3);
488 }
489 break;
490 case 0x8: /* picture coding extension */
491 if (bytes_left >= 5)
492 {
493 int picture_structure = bufptr[2] & 3;
494 int top_field_first = bufptr[3] & (1 << 7);
495 int repeat_first_field = bufptr[3] & (1 << 1);
496 int progressive_frame = bufptr[4] & (1 << 7);
497#if 0
498 LOG(VB_RECORD, LOG_DEBUG, LOC +
499 QString("picture_coding_extension(): (m_progressiveSequence: %1) picture_structure: %2 top_field_first: %3 repeat_first_field: %4 progressive_frame: %5")
500 .arg(QString::number(m_progressiveSequence , 2),
501 QString::number(picture_structure , 2),
502 QString::number(top_field_first , 2),
503 QString::number(repeat_first_field , 2),
504 QString::number(progressive_frame , 2)
505 )
506 );
507#endif
509 {
510 if (picture_structure == 0b00)
511 {
512 ; // reserved
513 }
514 else if (picture_structure == 0b11)
515 {
516 // frame picture (either interleaved interlaced or progressive)
517 hasFrame = true;
518 }
519 else if (picture_structure < 0b11)
520 {
521 // field picture
522 // Only add a frame for the first presented field.
523 // Do not add a frame for each field picture.
524 if (top_field_first != 0)
525 {
526 hasFrame = (picture_structure == 0b01); // Top Field
527 }
528 else // top_field_first == 0
529 {
530 hasFrame = (picture_structure == 0b10); // Bottom Field
531 }
532 }
533 }
534
535 /* check if we must repeat the frame */
536 m_repeatPict = 1;
537 if (repeat_first_field)
538 {
540 {
541 if (top_field_first)
542 m_repeatPict = 5;
543 else
544 m_repeatPict = 3;
545 }
546 else if (progressive_frame)
547 {
548 m_repeatPict = 2;
549 }
550 }
551 // The m_repeatPict code above matches
552 // mpegvideo_extract_headers(), but the
553 // code in mpeg_field_start() computes a
554 // value one less, which seems correct.
555 --m_repeatPict;
556 }
557 break;
558 }
559 }
560 }
561 if ((stream_id >= PESStreamID::MPEGVideoStreamBegin) &&
562 (stream_id <= PESStreamID::MPEGVideoStreamEnd))
563 {
564 int64_t pts = extract_timestamp(
565 bufptr, bytes_left, kExtractPTS);
566 int64_t dts = extract_timestamp(
567 bufptr, bytes_left, kExtractPTS);
568 HandleTimestamps(stream_id, pts, dts);
569 // Detect music choice program (very slow frame rate and audio)
570 if (m_firstKeyframe < 0
571 && m_tsLast[stream_id] - m_tsFirst[stream_id] > 3*90000LL)
572 {
573 hasKeyFrame = true;
574 m_musicChoice = true;
575 LOG(VB_GENERAL, LOG_INFO, LOC + "Music Choice program detected");
576 }
577 }
578 }
579 }
580
581 if (hasFrame && !hasKeyFrame)
582 {
583 // If we have seen kMaxKeyFrameDistance frames since the
584 // last GOP or SEQ stream_id, then pretend this picture
585 // is a keyframe. We may get artifacts but at least
586 // we will be able to skip frames.
587 hasKeyFrame = ((m_framesSeenCount & 0xf) == 0U);
588 hasKeyFrame &= (m_lastGopSeen + maxKFD) < m_framesSeenCount;
589 hasKeyFrame &= (m_lastSeqSeen + maxKFD) < m_framesSeenCount;
590 }
591
592 // m_bufferPackets will only be true if a payload start has been seen
593 if (hasKeyFrame && (m_bufferPackets || m_firstKeyframe >= 0))
594 {
595 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
596 ("Keyframe @ %1 + %2 = %3")
598 .arg(m_payloadBuffer.size())
600
603 }
604
605 if (hasFrame)
606 {
607 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
608 ("Frame @ %1 + %2 = %3")
610 .arg(m_payloadBuffer.size())
612
613 m_bufferPackets = false; // We now know if it is a keyframe, or not
617 else
618 {
619 /* Found a frame that is not a keyframe, and we want to
620 * start on a keyframe */
621 m_payloadBuffer.clear();
622 }
623 }
624
625 if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
626 {
627 m_videoAspect = aspectRatio;
629 }
630
631 if (height && width && (height != m_videoHeight || m_videoWidth != width))
632 {
633 m_videoHeight = height;
634 m_videoWidth = width;
636 }
637
638 if (frameRate.isNonzero() && frameRate != m_frameRate)
639 {
640 m_frameRate = frameRate;
641 LOG(VB_RECORD, LOG_INFO, LOC +
642 QString("FindMPEG2Keyframes: frame rate = %1")
643 .arg(frameRate.toDouble() * 1000));
644 FrameRateChange(frameRate.toDouble() * 1000, m_framesWrittenCount);
645 }
646
647 return m_firstKeyframe >= 0;
648}
649
650void DTVRecorder::HandleTimestamps(int stream_id, int64_t pts, int64_t dts)
651{
652 if (pts < 0)
653 {
654 m_tsLast[stream_id] = -1;
655 return;
656 }
657
658 if ((dts < 0) && !m_usePts)
659 {
660 m_tsLast[stream_id] = -1;
661 m_usePts = true;
662 LOG(VB_RECORD, LOG_DEBUG,
663 "Switching from dts tracking to pts tracking." +
664 QString("TS count is %1").arg(m_tsCount[stream_id]));
665 }
666
667 int64_t ts = dts;
668 int64_t gap_threshold = 90000; // 1 second
669 if (m_usePts)
670 {
671 ts = dts;
672 gap_threshold = 2*90000LL; // two seconds, compensate for GOP ordering
673 }
674
675 if (m_musicChoice)
676 gap_threshold = 8*90000LL; // music choice uses frames every 6 seconds
677
678 if (m_tsLast[stream_id] >= 0)
679 {
680 int64_t diff = ts - m_tsLast[stream_id];
681
682 // time jumped back more then 10 seconds, handle it as 33bit overflow
683 if (diff < (10 * -90000LL))
684 // MAX_PTS is 33bits all 1
685 diff += 0x1ffffffffLL;
686
687 // FIXME why do we handle negative gaps (aka overlap) like a gap?
688 if (diff < 0)
689 diff = -diff;
690
691 if (diff > gap_threshold && m_firstKeyframe >= 0)
692 {
693 QMutexLocker locker(&m_statisticsLock);
694
695 m_recordingGaps.push_back(
698 m_tsLast[stream_id], m_tsFirst[stream_id],
699 m_tsFirstDt[stream_id]),
701 ts, m_tsFirst[stream_id], m_tsFirstDt[stream_id])));
702 LOG(VB_RECORD, LOG_DEBUG, LOC + QString("Inserted gap %1 dur %2")
703 .arg(m_recordingGaps.back().toString()).arg(diff/90000.0));
704
706 {
708 if (recq.IsDamaged())
709 {
710 LOG(VB_GENERAL, LOG_INFO, LOC +
711 QString("HandleTimestamps: too much damage, "
712 "setting status to %1")
714 SetRecordingStatus(RecStatus::Failing, __FILE__, __LINE__);
715 }
716 }
717 }
718 }
719
720 m_tsLast[stream_id] = ts;
721
722 if (m_tsCount[stream_id] < 30)
723 {
724 if (!m_tsCount[stream_id] || (ts < m_tsFirst[stream_id]))
725 {
726 m_tsFirst[stream_id] = ts;
727 m_tsFirstDt[stream_id] = MythDate::current();
728 }
729 }
730
731 m_tsCount[stream_id]++;
732}
733
735{
740 {
742 m_tdTickCount = 0;
744 }
747 {
748 // not 1000 since m_tdTickCount needs to be divided by 2 to get an equivalent frame count
749 m_totalDuration = m_tdBase + ((int64_t) 500 * m_tdTickCount *
751 }
752
753 if (m_framesWrittenCount < 2000 || m_framesWrittenCount % 1000 == 0)
754 {
755 LOG(VB_RECORD, LOG_DEBUG,
756 QString("count=%1 m_frameRate=%2 tick_frameRate=%3 "
757 "tick_cnt=%4 tick_base=%5 _total_dur=%6")
759 .arg(m_frameRate.toString(),
761 .arg(m_tdTickCount)
762 .arg(m_tdBase)
763 .arg(m_totalDuration));
764 }
765}
766
768{
769 bool hasKeyFrame = false;
770 if (!m_ringBuffer || (GetStreamData()->VideoPIDSingleProgram() <= 0x1fff))
771 return hasKeyFrame;
772
773 static constexpr uint64_t kMsecPerDay { 24ULL * 60 * 60 * 1000 };
774 const double frame_interval = (1000.0 / m_videoFrameRate);
775 uint64_t elapsed = 0;
776 if (m_audioTimer.isValid())
777 elapsed = m_audioTimer.elapsed();
778 auto expected_frame = (uint64_t) ((double)elapsed / frame_interval);
779
780 while (m_framesSeenCount > expected_frame + 10000)
781 expected_frame += (uint64_t) ((double)kMsecPerDay / frame_interval);
782
783 if (!m_framesSeenCount || (m_framesSeenCount < expected_frame))
784 {
786 m_audioTimer.start();
787
788 m_bufferPackets = false;
790
791 if (1 == (m_framesSeenCount & 0x7))
792 {
795 hasKeyFrame = true;
796 }
797
800 }
801
802 return hasKeyFrame;
803}
804
808{
809 if (!m_ringBuffer || (GetStreamData()->VideoPIDSingleProgram() <= 0x1fff))
810 return true;
811
813 return true;
814
815 LOG(VB_RECORD, LOG_INFO, LOC + "DSMCC - FindOtherKeyframes() - "
816 "generating initial key-frame");
817
821
823
825
826 return true;
827}
828
834{
835 if (!m_ringBuffer)
836 return;
837
838 // Perform ringbuffer switch if needed.
840
841 uint64_t frameNum = m_framesWrittenCount;
842 if (m_firstKeyframe < 0)
843 {
844 m_firstKeyframe = frameNum;
845 SendMythSystemRecEvent("REC_STARTED_WRITING", m_curRecording);
846 }
847
848 // Add key frame to position map
849 m_positionMapLock.lock();
850 if (!m_positionMap.contains(frameNum))
851 {
852 int64_t startpos = m_ringBuffer->GetWritePosition() + extra;
853
854 // Don't put negative offsets into the database, they get munged into
855 // MAX_INT64 - offset, which is an exceedingly large number, and
856 // certainly not valid.
857 if (startpos >= 0)
858 {
859 m_positionMapDelta[frameNum] = startpos;
860 m_positionMap[frameNum] = startpos;
861 m_durationMap[frameNum] = llround(m_totalDuration);
862 m_durationMapDelta[frameNum] = llround(m_totalDuration);
863 }
864 }
865 m_positionMapLock.unlock();
866}
867
874{
875 if (!tspacket->HasPayload()) // no payload to scan
876 return m_firstKeyframe >= 0;
877
878 if (!m_ringBuffer)
879 {
880 LOG(VB_GENERAL, LOG_ERR, LOC + "FindH2645Keyframes: No ringbuffer");
881 return m_firstKeyframe >= 0;
882 }
883
884 if (!m_h2645Parser)
885 {
886 LOG(VB_GENERAL, LOG_ERR, LOC + "FindH2645Keyframes: m_h2645Parser not present");
887 return m_firstKeyframe >= 0;
888 }
889
890 const bool payloadStart = tspacket->PayloadStart();
891 if (payloadStart)
892 {
893 // reset PES sync state
894 m_pesSynced = false;
895 m_startCode = 0xffffffff;
896 }
897
898 uint aspectRatio = 0;
899 uint height = 0;
900 uint width = 0;
901 MythAVRational frameRate {0};
903
904 bool hasFrame = false;
905 bool hasKeyFrame = false;
906
907 // scan for PES packets and H.264 NAL units
908 uint i = tspacket->AFCOffset();
909 for (; i < TSPacket::kSize; ++i)
910 {
911 // special handling required when a new PES packet begins
912 if (payloadStart && !m_pesSynced)
913 {
914 // bounds check
915 if (i + 2 >= TSPacket::kSize)
916 {
917 LOG(VB_GENERAL, LOG_ERR, LOC +
918 "PES packet start code may overflow to next TS packet, "
919 "aborting keyframe search");
920 break;
921 }
922
923 // must find the PES start code
924 if (tspacket->data()[i ] != 0x00 ||
925 tspacket->data()[i+1] != 0x00 ||
926 tspacket->data()[i+2] != 0x01)
927 {
928 if (!m_pesTimer.isRunning() || m_pesTimer.elapsed() > 20000ms)
929 {
931 LOG(VB_GENERAL, LOG_ERR, LOC +
932 "PES start code not found in TS packet with PUSI set");
933 }
934 break;
935 }
936 i += 3;
937
939
940 // bounds check
941 if (i + 5 >= TSPacket::kSize)
942 {
943 LOG(VB_GENERAL, LOG_ERR, LOC +
944 "PES packet headers overflow to next TS packet, "
945 "aborting keyframe search");
946 break;
947 }
948
949 // now we need to compute where the PES payload begins
950 // skip past the stream_id (+1)
951 // the next two bytes are the PES packet length (+2)
952 // after that, one byte of PES packet control bits (+1)
953 // after that, one byte of PES header flags bits (+1)
954 // and finally, one byte for the PES header length
955 const unsigned char pes_header_length = tspacket->data()[i + 5];
956
957 // bounds check
958 if ((i + 6 + pes_header_length) >= TSPacket::kSize)
959 {
960 LOG(VB_GENERAL, LOG_ERR, LOC +
961 "PES packet headers overflow to next TS packet, "
962 "aborting keyframe search");
963 break;
964 }
965
966 // we now know where the PES payload is
967 // normally, we should have used 6, but use 5 because the for
968 // loop will bump i
969 i += 5 + pes_header_length;
970 m_pesSynced = true;
971
972#if 0
973 LOG(VB_RECORD, LOG_DEBUG, LOC + "PES synced");
974#endif
975 continue;
976 }
977
978 if (!m_pesSynced)
979 break;
980
981 // scan the NAL units
982 uint32_t bytes_used = m_h2645Parser->addBytes
983 (tspacket->data() + i, TSPacket::kSize - i,
985 i += (bytes_used - 1);
986
988 {
991 {
992 hasKeyFrame = m_h2645Parser->onKeyFrameStart();
993 hasFrame = true;
994 m_seenSps |= hasKeyFrame;
995
996 width = m_h2645Parser->pictureWidth();
997 height = m_h2645Parser->pictureHeight();
998 aspectRatio = m_h2645Parser->aspectRatio();
999 scantype = m_h2645Parser->GetScanType();
1000 frameRate = m_h2645Parser->getFrameRate();
1001 }
1002 }
1003 } // for (; i < TSPacket::kSize; ++i)
1004
1005 // If it has been more than 511 frames since the last keyframe,
1006 // pretend we have one.
1007 if (hasFrame && !hasKeyFrame &&
1009 {
1010 hasKeyFrame = true;
1011 LOG(VB_RECORD, LOG_WARNING, LOC +
1012 QString("FindH2645Keyframes: %1 frames without a keyframe.")
1014 }
1015
1016 // m_bufferPackets will only be true if a payload start has been seen
1017 if (hasKeyFrame && (m_bufferPackets || m_firstKeyframe >= 0))
1018 {
1019 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
1020 ("Keyframe @ %1 + %2 = %3 AU %4")
1022 .arg(m_payloadBuffer.size())
1025
1028 }
1029
1030 if (hasFrame)
1031 {
1032 LOG(VB_RECORD, LOG_DEBUG, LOC + QString
1033 ("Frame @ %1 + %2 = %3 AU %4")
1035 .arg(m_payloadBuffer.size())
1038
1039 m_bufferPackets = false; // We now know if this is a keyframe
1043 else
1044 {
1045 /* Found a frame that is not a keyframe, and we want to
1046 * start on a keyframe */
1047 m_payloadBuffer.clear();
1048 }
1049 }
1050
1051 if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
1052 {
1053 m_videoAspect = aspectRatio;
1055 }
1056
1057 if (height && width && (height != m_videoHeight || m_videoWidth != width))
1058 {
1059 m_videoHeight = height;
1060 m_videoWidth = width;
1061 ResolutionChange(width, height, m_framesWrittenCount);
1062 }
1063
1064 if (frameRate.isNonzero() && frameRate != m_frameRate)
1065 {
1066 LOG(VB_RECORD, LOG_INFO, LOC +
1067 QString("FindH2645Keyframes: timescale: %1, tick: %2, framerate: %3")
1068 .arg( m_h2645Parser->GetTimeScale() )
1069 .arg( m_h2645Parser->GetUnitsInTick() )
1070 .arg( frameRate.toDouble() * 1000 ) );
1071 m_frameRate = frameRate;
1072 FrameRateChange(frameRate.toDouble() * 1000, m_framesWrittenCount);
1073 }
1074
1075 if (scantype != SCAN_t::UNKNOWN_SCAN && scantype != m_scanType)
1076 {
1077 LOG(VB_RECORD, LOG_INFO, LOC +
1078 QString("FindH2645Keyframes: scan type: %1")
1079 .arg(scantype == SCAN_t::INTERLACED ?
1080 "Interlaced" : "Progressive"));
1081 m_scanType = scantype;
1083 }
1084
1085 return m_seenSps;
1086}
1087
1093{
1094 // Perform ringbuffer switch if needed.
1096
1097 uint64_t startpos = 0;
1098 uint64_t frameNum = m_framesWrittenCount;
1099
1100 if (m_firstKeyframe < 0)
1101 {
1102 m_firstKeyframe = frameNum;
1103 startpos = 0;
1104 SendMythSystemRecEvent("REC_STARTED_WRITING", m_curRecording);
1105 }
1106 else
1107 {
1109 }
1110
1111 // Add key frame to position map
1112 m_positionMapLock.lock();
1113 if (!m_positionMap.contains(frameNum))
1114 {
1115 m_positionMapDelta[frameNum] = startpos;
1116 m_positionMap[frameNum] = startpos;
1117 m_durationMap[frameNum] = llround(m_totalDuration);
1118 m_durationMapDelta[frameNum] = llround(m_totalDuration);
1119 }
1120 m_positionMapLock.unlock();
1121}
1122
1123void DTVRecorder::FindPSKeyFrames(const uint8_t *buffer, uint len)
1124{
1125 const uint maxKFD = kMaxKeyFrameDistance;
1126
1127 const uint8_t *bufstart = buffer;
1128 const uint8_t *bufptr = buffer;
1129 const uint8_t *bufend = buffer + len;
1130
1131 uint aspectRatio = 0;
1132 uint height = 0;
1133 uint width = 0;
1134 MythAVRational frameRate {0};
1135
1137 while (bufptr + skip < bufend)
1138 {
1139 bool hasFrame = false;
1140 bool hasKeyFrame = false;
1141
1142 const uint8_t *tmp = bufptr;
1143 bufptr = ByteReader::find_start_code_truncated(bufptr + skip, bufend, &m_startCode);
1146 m_videoBytesRemaining -= std::min(
1147 (uint)(bufptr - tmp), m_videoBytesRemaining);
1148
1150 continue;
1151
1152 // NOTE: Length may be zero for packets that only contain bytes from
1153 // video elementary streams in TS packets. 13818-1:2000 2.4.3.7
1154 int pes_packet_length = -1;
1155 if ((bufend - bufptr) >= 2)
1156 pes_packet_length = ((bufptr[0]<<8) | bufptr[1]) + 2 + 6;
1157
1158 const int stream_id = m_startCode & 0x000000ff;
1160 {
1161 if (PESStreamID::PictureStartCode == stream_id)
1162 { // pes_packet_length is meaningless
1163 pes_packet_length = -1;
1164 if (bufend-bufptr >= 4)
1165 {
1166 uint frmtypei = (bufptr[1]>>3) & 0x7;
1167 if ((1 <= frmtypei) && (frmtypei <= 5))
1168 hasFrame = true;
1169 }
1170 else
1171 {
1172 hasFrame = true;
1173 }
1174 }
1175 else if (PESStreamID::GOPStartCode == stream_id)
1176 { // pes_packet_length is meaningless
1177 pes_packet_length = -1;
1179 hasKeyFrame = true;
1180 }
1181 else if (PESStreamID::SequenceStartCode == stream_id)
1182 { // pes_packet_length is meaningless
1183 pes_packet_length = -1;
1185 hasKeyFrame |= (m_lastGopSeen + maxKFD)<m_framesSeenCount;
1186
1187 // Look for aspectRatio changes and store them in the database
1188 aspectRatio = (bufptr[3] >> 4);
1189
1190 // Get resolution
1191 height = ((bufptr[1] & 0xf) << 8) | bufptr[2];
1192 width = (bufptr[0] <<4) | (bufptr[1]>>4);
1193
1194 frameRate = frameRateMap[(bufptr[3] & 0x0000000f)];
1195 }
1196 }
1197 else if (!m_audioBytesRemaining)
1198 {
1199 if ((stream_id >= PESStreamID::MPEGVideoStreamBegin) &&
1200 (stream_id <= PESStreamID::MPEGVideoStreamEnd))
1201 { // ok-dvdinfo
1202 m_videoBytesRemaining = std::max(0, pes_packet_length);
1203 }
1204 else if ((stream_id >= PESStreamID::MPEGAudioStreamBegin) &&
1205 (stream_id <= PESStreamID::MPEGAudioStreamEnd))
1206 { // ok-dvdinfo
1207 m_audioBytesRemaining = std::max(0, pes_packet_length);
1208 }
1209 }
1210
1211 if (PESStreamID::PaddingStream == stream_id)
1212 { // ok-dvdinfo
1213 m_otherBytesRemaining = std::max(0, pes_packet_length);
1214 }
1215
1216 m_startCode = 0xffffffff; // reset start code
1217
1218 if (hasFrame && !hasKeyFrame)
1219 {
1220 // If we have seen kMaxKeyFrameDistance frames since the
1221 // last GOP or SEQ stream_id, then pretend this picture
1222 // is a keyframe. We may get artifacts but at least
1223 // we will be able to skip frames.
1224 hasKeyFrame = ((m_framesSeenCount & 0xf) == 0U);
1225 hasKeyFrame &= (m_lastGopSeen + maxKFD) < m_framesSeenCount;
1226 hasKeyFrame &= (m_lastSeqSeen + maxKFD) < m_framesSeenCount;
1227 }
1228
1229 if (hasFrame)
1230 {
1234 }
1235
1236 if (hasKeyFrame)
1237 {
1239 HandleKeyframe((int64_t)m_payloadBuffer.size() - (bufptr - bufstart));
1240 }
1241
1242 if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
1243 {
1244 m_videoAspect = aspectRatio;
1246 }
1247
1248 if (height && width &&
1249 (height != m_videoHeight || m_videoWidth != width))
1250 {
1251 m_videoHeight = height;
1252 m_videoWidth = width;
1253 ResolutionChange(width, height, m_framesWrittenCount);
1254 }
1255
1256 if (frameRate.isNonzero() && frameRate != m_frameRate)
1257 {
1258 m_frameRate = frameRate;
1259 LOG(VB_RECORD, LOG_INFO, LOC +
1260 QString("FindPSKeyFrames: frame rate = %1")
1261 .arg(frameRate.toDouble() * 1000));
1262 FrameRateChange(frameRate.toDouble() * 1000, m_framesWrittenCount);
1263 }
1264
1265 if (hasKeyFrame || hasFrame)
1266 {
1267 // We are free to write the packet, but if we have
1268 // buffered packet[s] we have to write them first...
1269 if (!m_payloadBuffer.empty())
1270 {
1271 if (m_ringBuffer)
1272 {
1274 (m_payloadBuffer).data(), m_payloadBuffer.size());
1275 }
1276 m_payloadBuffer.clear();
1277 }
1278
1279 if (m_ringBuffer)
1280 m_ringBuffer->Write(bufstart, (bufptr - bufstart));
1281
1282 bufstart = bufptr;
1283 }
1284
1286 }
1287
1288 int bytes_skipped = bufend - bufptr;
1289 if (bytes_skipped > 0)
1290 {
1291 m_audioBytesRemaining -= std::min(
1292 (uint)bytes_skipped, m_audioBytesRemaining);
1293 m_videoBytesRemaining -= std::min(
1294 (uint)bytes_skipped, m_videoBytesRemaining);
1295 m_otherBytesRemaining -= std::min(
1296 (uint)bytes_skipped, m_otherBytesRemaining);
1297 }
1298
1299 uint64_t idx = m_payloadBuffer.size();
1300 uint64_t rem = (bufend - bufstart);
1301 m_payloadBuffer.resize(idx + rem);
1302 memcpy(&m_payloadBuffer[idx], bufstart, rem);
1303#if 0
1304 LOG(VB_GENERAL, LOG_DEBUG, LOC +
1305 QString("idx: %1, rem: %2").arg(idx).arg(rem));
1306#endif
1307}
1308
1310{
1311 if (!_pat)
1312 {
1313 LOG(VB_RECORD, LOG_ERR, LOC + "SetPAT(NULL)");
1314 return;
1315 }
1316
1317 QMutexLocker change_lock(&m_pidLock);
1318
1319 int progNum = m_streamData->DesiredProgram();
1320 uint pmtpid = _pat->FindPID(progNum);
1321
1322 // If we have not found the desired program in the PAT and this happens to be
1323 // an SPTS then update the desired program to the one that is present in the PAT.
1324 if (!pmtpid)
1325 {
1326 if (_pat->ProgramCount() == 1)
1327 {
1328 int oldProgNum = progNum;
1329 progNum = _pat->ProgramNumber(0);
1330 LOG(VB_GENERAL, LOG_INFO, LOC +
1331 QString("Update desired program found in SPTS PAT from %1 to %2")
1332 .arg(oldProgNum).arg(progNum));
1333 GetStreamData()->SetDesiredProgram(progNum);
1334 pmtpid = _pat->FindPID(progNum);
1335 }
1336 }
1337 if (!pmtpid)
1338 {
1339 LOG(VB_RECORD, LOG_ERR, LOC +
1340 QString("SetPAT(): Ignoring PAT not containing our desired "
1341 "program (%1)...").arg(progNum));
1342 return;
1343 }
1344
1345 LOG(VB_RECORD, LOG_INFO, LOC + QString("SetPAT(%1 on pid 0x%2)")
1346 .arg(progNum).arg(pmtpid,0,16));
1347
1350 delete oldpat;
1351
1352 // Listen for the other PMTs for faster channel switching
1353 for (uint i = 0; m_inputPat && (i < m_inputPat->ProgramCount()); ++i)
1354 {
1355 uint pmt_pid = m_inputPat->ProgramPID(i);
1356 if (!m_streamData->IsListeningPID(pmt_pid))
1358 }
1359}
1360
1362{
1363 QMutexLocker change_lock(&m_pidLock);
1364
1365 LOG(VB_RECORD, LOG_INFO, LOC + QString("SetPMT(%1, %2)").arg(progNum)
1366 .arg(_pmt == nullptr ? "NULL" : "valid"));
1367
1368
1369 if ((int)progNum == m_streamData->DesiredProgram())
1370 {
1371 LOG(VB_RECORD, LOG_INFO, LOC + QString("SetPMT(%1)").arg(progNum));
1372 ProgramMapTable *oldpmt = m_inputPmt;
1373 m_inputPmt = new ProgramMapTable(*_pmt);
1374
1375 QString sistandard = GetSIStandard();
1376
1377 bool has_no_av = true;
1378 for (uint i = 0; i < m_inputPmt->StreamCount() && has_no_av; ++i)
1379 {
1380 has_no_av &= !m_inputPmt->IsVideo(i, sistandard);
1381 has_no_av &= !m_inputPmt->IsAudio(i, sistandard);
1382 }
1383 m_hasNoAV = has_no_av;
1384
1386 delete oldpmt;
1387 }
1388}
1389
1391 bool insert)
1392{
1393 if (!pat)
1394 {
1395 LOG(VB_RECORD, LOG_ERR, LOC + "HandleSingleProgramPAT(NULL)");
1396 return;
1397 }
1398
1399 if (!m_ringBuffer)
1400 return;
1401
1402 uint next_cc = (pat->tsheader()->ContinuityCounter()+1)&0xf;
1403 pat->tsheader()->SetContinuityCounter(next_cc);
1404 pat->GetAsTSPackets(m_scratch, next_cc);
1405
1406 for (const auto & tspacket : m_scratch)
1407 DTVRecorder::BufferedWrite(tspacket, insert);
1408}
1409
1411{
1412 if (!pmt)
1413 {
1414 LOG(VB_RECORD, LOG_ERR, LOC + "HandleSingleProgramPMT(NULL)");
1415 return;
1416 }
1417
1418 // We only want to do these checks once per recording
1419 bool seenVideo = (m_primaryVideoCodec != AV_CODEC_ID_NONE);
1420 bool seenAudio = (m_primaryAudioCodec != AV_CODEC_ID_NONE);
1421 uint bestAudioCodec = 0;
1422 // collect stream types for H.264 (MPEG-4 AVC) keyframe detection
1423 for (uint i = 0; i < pmt->StreamCount(); ++i)
1424 {
1425 // We only care about the first identifiable video stream
1426 if (!seenVideo && (m_primaryVideoCodec == AV_CODEC_ID_NONE) &&
1428 {
1429 seenVideo = true; // Ignore other video streams
1430 switch (pmt->StreamType(i))
1431 {
1433 m_primaryVideoCodec = AV_CODEC_ID_MPEG1VIDEO;
1434 break;
1436 m_primaryVideoCodec = AV_CODEC_ID_MPEG2VIDEO;
1437 break;
1439 m_primaryVideoCodec = AV_CODEC_ID_MPEG4;
1440 break;
1442 m_primaryVideoCodec = AV_CODEC_ID_H264;
1443 if (dynamic_cast<AVCParser *>(m_h2645Parser) == nullptr)
1444 {
1445 delete m_h2645Parser;
1446 m_h2645Parser = nullptr;
1447 }
1448 if (m_h2645Parser == nullptr)
1449 {
1450 auto * avcParser = new AVCParser;
1451 if (avcParser != nullptr)
1452 {
1453 m_h2645Parser = reinterpret_cast<H2645Parser *>
1454 (avcParser);
1455 avcParser->use_I_forKeyframes(m_useIForKeyframe);
1456 }
1457 }
1458 break;
1460 LOG(VB_GENERAL, LOG_INFO, LOC + "HEVC detected");
1461 m_primaryVideoCodec = AV_CODEC_ID_H265;
1462 if (dynamic_cast<HEVCParser *>(m_h2645Parser) == nullptr)
1463 {
1464 delete m_h2645Parser;
1465 m_h2645Parser = nullptr;
1466 }
1467 if (m_h2645Parser == nullptr)
1468 {
1469 m_h2645Parser = reinterpret_cast<H2645Parser *>
1470 (new HEVCParser);
1471 }
1472 break;
1474 m_primaryVideoCodec = AV_CODEC_ID_MPEG2VIDEO; // TODO Will it always be MPEG2?
1475 break;
1476 case StreamID::VC1Video:
1477 m_primaryVideoCodec = AV_CODEC_ID_VC1;
1478 break;
1479 default:
1480 break;
1481 }
1482
1483 if (m_primaryVideoCodec != AV_CODEC_ID_NONE)
1485 }
1486
1487 // We want the 'best' identifiable audio stream, where 'best' is
1488 // subjective and no-one will likely agree.
1489 // For now it's the 'best' codec, assuming mpeg stream types range
1490 // from worst to best, which it does
1491 if (!seenAudio && StreamID::IsAudio(pmt->StreamType(i)) &&
1492 pmt->StreamType(i) > bestAudioCodec)
1493 {
1494 bestAudioCodec = pmt->StreamType(i);
1495 switch (pmt->StreamType(i))
1496 {
1497 case StreamID::MPEG1Audio: // MPEG-1 Layer 2 (MP2)
1498 case StreamID::MPEG2Audio: // MPEG-2 Part 3 (MP2 Multichannel)
1499 m_primaryAudioCodec = AV_CODEC_ID_MP2;
1500 break;
1502 m_primaryAudioCodec = AV_CODEC_ID_AAC;
1503 break;
1505 m_primaryAudioCodec = AV_CODEC_ID_AAC_LATM;
1506 break;
1507 case StreamID::AC3Audio:
1508 m_primaryAudioCodec = AV_CODEC_ID_AC3;
1509 break;
1511 m_primaryAudioCodec = AV_CODEC_ID_EAC3;
1512 break;
1513 case StreamID::DTSAudio:
1514 m_primaryAudioCodec = AV_CODEC_ID_DTS;
1515 break;
1516 default:
1517 break;
1518 }
1519
1520 if (m_primaryAudioCodec != AV_CODEC_ID_NONE)
1522 }
1523
1524// LOG(VB_GENERAL, LOG_DEBUG, QString("Recording(%1): Stream #%2: %3 ")
1525// .arg(m_curRecording ? QString::number(m_curRecording->GetRecordingID()) : "")
1526// .arg(i)
1527// .arg(StreamID::GetDescription(pmt->StreamType(i))));
1528 m_streamId[pmt->StreamPID(i)] = pmt->StreamType(i);
1529 }
1530
1531 // If the PCRPID is valid and the PCR is not contained
1532 // in another stream, make sure the PCR stream is not
1533 // discarded (use PrivSec type as dummy 'valid' value)
1534 if (pmt->PCRPID() != 0x1fff && pmt->FindPID(pmt->PCRPID()) == -1)
1536
1537 if (!m_ringBuffer)
1538 return;
1539
1540 uint next_cc = (pmt->tsheader()->ContinuityCounter()+1)&0xf;
1541 pmt->tsheader()->SetContinuityCounter(next_cc);
1542 pmt->GetAsTSPackets(m_scratch, next_cc);
1543
1544 for (const auto & tspacket : m_scratch)
1545 DTVRecorder::BufferedWrite(tspacket, insert);
1546}
1547
1549{
1550 const uint pid = tspacket.PID();
1551
1552 if (pid != 0x1fff)
1553 m_packetCount.fetchAndAddAcquire(1);
1554
1555 // Check continuity counter
1556 uint old_cnt = m_continuityCounter[pid];
1557 if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter()))
1558 {
1559 int v = m_continuityErrorCount.fetchAndAddRelaxed(1) + 1;
1560 double erate = v * 100.0 / m_packetCount.fetchAndAddRelaxed(0);
1561 LOG(VB_RECORD, LOG_WARNING, LOC +
1562 QString("PID 0x%1 discontinuity detected ((%2+1)%16!=%3) %4%")
1563 .arg(pid,0,16).arg(old_cnt,2)
1564 .arg(tspacket.ContinuityCounter(),2)
1565 .arg(erate));
1566 }
1567
1568 // Only create fake keyframe[s] if there are no audio/video streams
1569 if (m_inputPmt && m_hasNoAV)
1570 {
1571 FindOtherKeyframes(&tspacket);
1572 m_bufferPackets = false;
1573 }
1574 else if (m_recordMptsOnly)
1575 {
1576 /* When recording the full, unfiltered, MPTS, trigger a write
1577 * every 0.5 seconds. Since the packets are unfiltered and
1578 * unprocessed we cannot wait for a keyframe to trigger the
1579 * writes. */
1580
1581 if (m_framesSeenCount++ == 0)
1583
1584 if (m_recordMptsTimer.elapsed() > 0.5s)
1585 {
1590 }
1591 }
1592 else if (m_streamId[pid] == 0)
1593 {
1594 // Ignore this packet if the PID should be stripped
1595 return true;
1596 }
1597 else
1598 {
1599 // There are audio/video streams. Only write the packet
1600 // if audio/video key-frames have been found
1602 return true;
1603 }
1604
1605 BufferedWrite(tspacket);
1606
1607 return true;
1608}
1609
1611{
1612 if (!m_ringBuffer)
1613 return true;
1614
1615 uint streamType = m_streamId[tspacket.PID()];
1616
1617 if (tspacket.HasPayload() && tspacket.PayloadStart())
1618 {
1619 if (m_bufferPackets && m_firstKeyframe >= 0 && !m_payloadBuffer.empty())
1620 {
1621 // Flush the buffer
1622 if (m_ringBuffer)
1624 m_payloadBuffer.clear();
1625 }
1626
1627 // buffer packets until we know if this is a keyframe
1628 m_bufferPackets = true;
1629 }
1630
1631 // Check for keyframes and count frames
1632 if (streamType == StreamID::H264Video ||
1633 streamType == StreamID::H265Video)
1634 FindH2645Keyframes(&tspacket);
1635 else if (streamType != 0)
1636 FindMPEG2Keyframes(&tspacket);
1637 else
1638 LOG(VB_RECORD, LOG_ERR, LOC +
1639 "ProcessVideoTSPacket: unknown stream type!");
1640
1641 return ProcessAVTSPacket(tspacket);
1642}
1643
1645{
1646 if (!m_ringBuffer)
1647 return true;
1648
1649 if (tspacket.HasPayload() && tspacket.PayloadStart())
1650 {
1651 if (m_bufferPackets && m_firstKeyframe >= 0 && !m_payloadBuffer.empty())
1652 {
1653 // Flush the buffer
1654 if (m_ringBuffer)
1656 m_payloadBuffer.clear();
1657 }
1658
1659 // buffer packets until we know if this is a keyframe
1660 m_bufferPackets = true;
1661 }
1662
1663 FindAudioKeyframes(&tspacket);
1664 return ProcessAVTSPacket(tspacket);
1665}
1666
1669{
1670 // Sync recording start to first keyframe
1672 {
1673 if (m_bufferPackets)
1674 BufferedWrite(tspacket);
1675 return true;
1676 }
1677
1678 const uint pid = tspacket.PID();
1679
1680 if (pid != 0x1fff)
1681 m_packetCount.fetchAndAddAcquire(1);
1682
1683 // Check continuity counter
1684 uint old_cnt = m_continuityCounter[pid];
1685 if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter()))
1686 {
1687 int v = m_continuityErrorCount.fetchAndAddRelaxed(1) + 1;
1688 double erate = v * 100.0 / m_packetCount.fetchAndAddRelaxed(0);
1689 LOG(VB_RECORD, LOG_WARNING, LOC +
1690 QString("A/V PID 0x%1 discontinuity detected ((%2+1)%16!=%3) %4%")
1691 .arg(pid,0,16).arg(old_cnt).arg(tspacket.ContinuityCounter())
1692 .arg(erate,5,'f',2));
1693 }
1694
1695 if (!(m_pidStatus[pid] & kPayloadStartSeen))
1696 {
1698 LOG(VB_RECORD, LOG_INFO, LOC +
1699 QString("PID 0x%1 Found Payload Start").arg(pid,0,16));
1700 }
1701
1702 BufferedWrite(tspacket);
1703
1704 return true;
1705}
1706
1708{
1710 recq->AddTSStatistics(
1711 m_continuityErrorCount.fetchAndAddRelaxed(0),
1712 m_packetCount.fetchAndAddRelaxed(0));
1713 return recq;
1714}
1715
1716/* 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:67
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:52
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:92
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:81
uint pictureHeight(void) const
Definition: H2645Parser.h:65
uint aspectRatio(void) const
Computes aspect ratio from picture size and sample aspect ratio.
bool stateChanged(void) const
Definition: H2645Parser.h:59
virtual MythAVRational getFrameRate() const =0
virtual field_type getFieldType(void) const =0
bool onFrameStart(void) const
Definition: H2645Parser.h:61
uint pictureWidth(void) const
Definition: H2645Parser.h:64
uint32_t GetUnitsInTick(void) const
Definition: H2645Parser.h:80
virtual uint32_t addBytes(const uint8_t *bytes, uint32_t byte_count, uint64_t stream_offset)=0
bool onKeyFrameStart(void) const
Definition: H2645Parser.h:62
uint32_t GetTimeScale(void) const
Definition: H2645Parser.h:79
uint64_t keyframeAUstreamOffset(void) const
Definition: H2645Parser.h:76
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:133
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:451
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:540
bool IsVideo(uint i, const QString &sistandard) const
Returns true iff the stream at index i is a video stream.
Definition: mpegtables.cpp:518
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:50
QAtomicInt m_timeOfLatestDataPacketInterval
Definition: recorderbase.h:354
static constexpr std::chrono::milliseconds kTimeOfLatestDataIntervalTarget
timeOfLatest update interval target in milliseconds.
Definition: recorderbase.h:359
AVContainer m_containerFormat
Definition: recorderbase.h:294
uint m_videoHeight
Definition: recorderbase.h:306
virtual bool CheckForRingBufferSwitch(void)
If requested, switch to new RingBuffer/ProgramInfo objects.
double m_videoFrameRate
Definition: recorderbase.h:302
frm_pos_map_t m_positionMap
Definition: recorderbase.h:334
AVCodecID m_primaryAudioCodec
Definition: recorderbase.h:296
uint m_videoAspect
Definition: recorderbase.h:304
frm_pos_map_t m_durationMapDelta
Definition: recorderbase.h:337
QDateTime m_timeOfLatestData
Definition: recorderbase.h:355
QMutex m_statisticsLock
Definition: recorderbase.h:350
MythTimer m_timeOfLatestDataTimer
Definition: recorderbase.h:356
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:352
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:335
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:295
virtual void SetRecordingStatus(RecStatus::Type status, const QString &file, int line)
frm_pos_map_t m_durationMap
Definition: recorderbase.h:336
void SetStrOption(RecordingProfile *profile, const QString &name)
Convenience function used to set QString options from a profile.
MythMediaBuffer * m_ringBuffer
Definition: recorderbase.h:291
RecordingGaps m_recordingGaps
Definition: recorderbase.h:357
void SetDuration(std::chrono::milliseconds duration)
Note the total duration in the recordedmark table.
MythAVRational m_frameRate
Definition: recorderbase.h:308
QAtomicInt m_timeOfLatestDataCount
Definition: recorderbase.h:353
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:351
QMutex m_positionMapLock
Definition: recorderbase.h:333
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:310
virtual void ClearStatistics(void)
void SetPositionMapType(MarkTypes type)
Set seektable type.
Definition: recorderbase.h:254
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:143
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:38
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
unsigned int uint
Definition: freesurround.h:24
static guint32 * tmp
Definition: goom_core.cpp:26
@ kPIDPriorityLow
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::milliseconds > millisecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:91
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:54
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:74
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:55
@ MARK_GOP_BYFRAME
Definition: programtypes.h:63
@ MARK_DURATION_MS
Definition: programtypes.h:73
@ formatMPEG2_TS
Definition: recordingfile.h:16
@ kSingleRecord
SCAN_t
Definition: scantype.h:6
@ INTERLACED
@ UNKNOWN_SCAN