From 71ad653b9a7ba593eb5307d4cd88a81e4d9387a4 Mon Sep 17 00:00:00 2001 From: John Poet Date: Sat, 2 Mar 2013 22:20:02 -0500 Subject: [PATCH] DTVRecorder: buffer up each payload until we know if the payload is a keyframe. Especially with H.264, we can be several packets into a payload before we know if it is a keyframe or just a frame or something else. Always buffer the packets of a payload until we know what we are doing with the payload. If we need to switch ringbuffers, we now do it so the new file will start at the beginning of the payload holding a keyframe. Each file will also start with a PAT/PMT if the "recorder" calls for it. (cherry picked from commit 87a15e1ce40d81058ff221308d666d73b1517ff2) Signed-off-by: Taylor Ralph --- mythtv/libs/libmythtv/asirecorder.cpp | 15 +- mythtv/libs/libmythtv/asirecorder.h | 1 + mythtv/libs/libmythtv/cetonrecorder.cpp | 14 +- mythtv/libs/libmythtv/cetonrecorder.h | 1 + mythtv/libs/libmythtv/dtvrecorder.cpp | 303 ++++++++++-------- mythtv/libs/libmythtv/dtvrecorder.h | 8 +- mythtv/libs/libmythtv/dvbrecorder.cpp | 14 +- mythtv/libs/libmythtv/dvbrecorder.h | 1 + mythtv/libs/libmythtv/hdhrrecorder.cpp | 14 +- mythtv/libs/libmythtv/hdhrrecorder.h | 1 + mythtv/libs/libmythtv/mpeg/mpegstreamdata.cpp | 8 +- mythtv/libs/libmythtv/mpeg/streamlisteners.h | 5 +- mythtv/libs/libmythtv/mpegrecorder.cpp | 9 +- 13 files changed, 227 insertions(+), 167 deletions(-) diff --git a/mythtv/libs/libmythtv/asirecorder.cpp b/mythtv/libs/libmythtv/asirecorder.cpp index 830ac233ff9..883d83bf53c 100644 --- a/mythtv/libs/libmythtv/asirecorder.cpp +++ b/mythtv/libs/libmythtv/asirecorder.cpp @@ -62,6 +62,14 @@ void ASIRecorder::SetOption(const QString &name, int value) DTVRecorder::SetOption(name, value); } +void ASIRecorder::StartNewFile(void) +{ + // Make sure the first things in the file are a PAT & PMT + HandleSingleProgramPAT(_stream_data->PATSingleProgram(), true); + HandleSingleProgramPMT(_stream_data->PMTSingleProgram(), true); +} + + void ASIRecorder::run(void) { if (!Open()) @@ -99,12 +107,7 @@ void ASIRecorder::run(void) if (m_channel && (m_channel->GetSIStandard() == "dvb")) _stream_data->AddListeningPID(DVB_TDT_PID); - // Make sure the first things in the file are a PAT & PMT - bool tmp = _wait_for_keyframe_option; - _wait_for_keyframe_option = false; - HandleSingleProgramPAT(_stream_data->PATSingleProgram()); - HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); - _wait_for_keyframe_option = tmp; + StartNewFile(); _stream_data->AddAVListener(this); _stream_data->AddWritingListener(this); diff --git a/mythtv/libs/libmythtv/asirecorder.h b/mythtv/libs/libmythtv/asirecorder.h index f29ccc5ee03..06c5bc4cec1 100644 --- a/mythtv/libs/libmythtv/asirecorder.h +++ b/mythtv/libs/libmythtv/asirecorder.h @@ -68,6 +68,7 @@ class ASIRecorder : public DTVRecorder bool Open(void); bool IsOpen(void) const; void Close(void); + void StartNewFile(void); private: ASIChannel *m_channel; diff --git a/mythtv/libs/libmythtv/cetonrecorder.cpp b/mythtv/libs/libmythtv/cetonrecorder.cpp index f7aacf4d4fe..b923770a468 100644 --- a/mythtv/libs/libmythtv/cetonrecorder.cpp +++ b/mythtv/libs/libmythtv/cetonrecorder.cpp @@ -46,6 +46,13 @@ void CetonRecorder::Close(void) LOG(VB_RECORD, LOG_INFO, LOC + "Close() -- end"); } +void CetonRecorder::StartNewFile(void) +{ + // Make sure the first things in the file are a PAT & PMT + HandleSingleProgramPAT(_stream_data->PATSingleProgram(), true); + HandleSingleProgramPMT(_stream_data->PMTSingleProgram(), true); +} + void CetonRecorder::run(void) { LOG(VB_RECORD, LOG_INFO, LOC + "run -- begin"); @@ -65,12 +72,7 @@ void CetonRecorder::run(void) recordingWait.wakeAll(); } - // Make sure the first things in the file are a PAT & PMT - bool tmp = _wait_for_keyframe_option; - _wait_for_keyframe_option = false; - HandleSingleProgramPAT(_stream_data->PATSingleProgram()); - HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); - _wait_for_keyframe_option = tmp; + StartNewFile(); _stream_data->AddAVListener(this); _stream_data->AddWritingListener(this); diff --git a/mythtv/libs/libmythtv/cetonrecorder.h b/mythtv/libs/libmythtv/cetonrecorder.h index fe2d1766d45..8233d4602fa 100644 --- a/mythtv/libs/libmythtv/cetonrecorder.h +++ b/mythtv/libs/libmythtv/cetonrecorder.h @@ -26,6 +26,7 @@ class CetonRecorder : public DTVRecorder bool Open(void); void Close(void); + void StartNewFile(void); bool IsOpen(void) const { return _stream_handler; } diff --git a/mythtv/libs/libmythtv/dtvrecorder.cpp b/mythtv/libs/libmythtv/dtvrecorder.cpp index 9d7d89fbbbb..518e13b5914 100644 --- a/mythtv/libs/libmythtv/dtvrecorder.cpp +++ b/mythtv/libs/libmythtv/dtvrecorder.cpp @@ -85,7 +85,12 @@ DTVRecorder::DTVRecorder(TVRec *rec) : { SetPositionMapType(MARK_GOP_BYFRAME); _payload_buffer.reserve(TSPacket::kSize * (50 + 1)); + ResetForNewFile(); + + memset(_stream_id, 0, sizeof(_stream_id)); + memset(_pid_status, 0, sizeof(_pid_status)); + memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); } DTVRecorder::~DTVRecorder() @@ -145,14 +150,7 @@ void DTVRecorder::SetOptionsFromProfile(RecordingProfile *profile, void DTVRecorder::FinishRecording(void) { if (ringBuffer) - { - if (!_payload_buffer.empty()) - { - ringBuffer->Write(&_payload_buffer[0], _payload_buffer.size()); - _payload_buffer.clear(); - } ringBuffer->WriterFlush(); - } if (curRecording) { @@ -167,7 +165,7 @@ void DTVRecorder::ResetForNewFile(void) LOG(VB_RECORD, LOG_INFO, LOC + "ResetForNewFile(void)"); QMutexLocker locker(&positionMapLock); - // _first_keyframe, _seen_psp and m_h264_parser should + // _seen_psp and m_h264_parser should // not be reset here. This will only be called just as // we're seeing the first packet of a new keyframe for // writing to the new file and anything that makes the @@ -176,7 +174,7 @@ void DTVRecorder::ResetForNewFile(void) // -- Daniel Kristjansson 2011-02-26 _start_code = 0xffffffff; - //_first_keyframe + _first_keyframe = -1; _has_written_other_keyframe = false; _last_keyframe_seen = 0; _last_gop_seen = 0; @@ -187,18 +185,13 @@ void DTVRecorder::ResetForNewFile(void) //_recording _error = QString(); - memset(_stream_id, 0, sizeof(_stream_id)); - memset(_pid_status, 0, sizeof(_pid_status)); - memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); - _progressive_sequence = 0; _repeat_pict = 0; - _pes_synced = false; + //_pes_synced //_seen_sps positionMap.clear(); positionMapDelta.clear(); - _payload_buffer.clear(); locker.unlock(); ClearStatistics(); @@ -209,9 +202,9 @@ void DTVRecorder::ClearStatistics(void) RecorderBase::ClearStatistics(); memset(_ts_count, 0, sizeof(_ts_count)); - for (int i = 0; i < 256; i++) + for (int i = 0; i < 256; ++i) _ts_last[i] = -1LL; - for (int i = 0; i < 256; i++) + for (int i = 0; i < 256; ++i) _ts_first[i] = -1LL; //_ts_first_dt -- doesn't need to be cleared only used if _ts_first>=0 _packet_count.fetchAndStoreRelaxed(0); @@ -264,58 +257,62 @@ void DTVRecorder::SetStreamData(void) _stream_data->SetDesiredProgram(_stream_data->DesiredProgram()); } -void DTVRecorder::BufferedWrite(const TSPacket &tspacket) +void DTVRecorder::BufferedWrite(const TSPacket &tspacket, bool insert) { - // delay until first GOP to avoid decoder crash on res change - if (_wait_for_keyframe_option && _first_keyframe<0) - return; - - if (curRecording && timeOfFirstDataIsSet.testAndSetRelaxed(0,1)) + if (!insert) // PAT/PMT may need inserted in front of any buffered data { - QMutexLocker locker(&statisticsLock); - timeOfFirstData = mythCurrentDateTime(); - timeOfLatestData = mythCurrentDateTime(); - timeOfLatestDataTimer.start(); - } + // delay until first GOP to avoid decoder crash on res change + if (!_buffer_packets && _wait_for_keyframe_option && + _first_keyframe < 0) + return; - int val = timeOfLatestDataCount.fetchAndAddRelaxed(1); - int thresh = timeOfLatestDataPacketInterval.fetchAndAddRelaxed(0); - if (val > thresh) - { - QMutexLocker locker(&statisticsLock); - uint elapsed = timeOfLatestDataTimer.restart(); - int interval = thresh; - if (elapsed > kTimeOfLatestDataIntervalTarget + 250) - interval = timeOfLatestDataPacketInterval - .fetchAndStoreRelaxed(thresh * 4 / 5); - else if (elapsed + 250 < kTimeOfLatestDataIntervalTarget) - interval = timeOfLatestDataPacketInterval - .fetchAndStoreRelaxed(thresh * 9 / 8); - - timeOfLatestDataCount.fetchAndStoreRelaxed(1); - timeOfLatestData = mythCurrentDateTime(); - - LOG(VB_RECORD, LOG_DEBUG, LOC + QString("Updating timeOfLatestData ") + - QString("elapsed(%1) interval(%2)") - .arg(elapsed).arg(interval)); - } + if (curRecording && timeOfFirstDataIsSet.testAndSetRelaxed(0,1)) + { + QMutexLocker locker(&statisticsLock); + timeOfFirstData = mythCurrentDateTime(); + timeOfLatestData = mythCurrentDateTime(); + timeOfLatestDataTimer.start(); + } - // Do we have to buffer the packet for exact keyframe detection? - if (_buffer_packets) - { - int idx = _payload_buffer.size(); - _payload_buffer.resize(idx + TSPacket::kSize); - memcpy(&_payload_buffer[idx], tspacket.data(), TSPacket::kSize); - return; - } + int val = timeOfLatestDataCount.fetchAndAddRelaxed(1); + int thresh = timeOfLatestDataPacketInterval.fetchAndAddRelaxed(0); + if (val > thresh) + { + QMutexLocker locker(&statisticsLock); + uint elapsed = timeOfLatestDataTimer.restart(); + int interval = thresh; + if (elapsed > kTimeOfLatestDataIntervalTarget + 250) + interval = timeOfLatestDataPacketInterval + .fetchAndStoreRelaxed(thresh * 4 / 5); + else if (elapsed + 250 < kTimeOfLatestDataIntervalTarget) + interval = timeOfLatestDataPacketInterval + .fetchAndStoreRelaxed(thresh * 9 / 8); + + timeOfLatestDataCount.fetchAndStoreRelaxed(1); + timeOfLatestData = mythCurrentDateTime(); + + LOG(VB_RECORD, LOG_DEBUG, LOC + + QString("Updating timeOfLatestData elapsed(%1) interval(%2)") + .arg(elapsed).arg(interval)); + } - // We are free to write the packet, but if we have buffered packet[s] - // we have to write them first... - if (!_payload_buffer.empty()) - { - if (ringBuffer) - ringBuffer->Write(&_payload_buffer[0], _payload_buffer.size()); - _payload_buffer.clear(); + // Do we have to buffer the packet for exact keyframe detection? + if (_buffer_packets) + { + int idx = _payload_buffer.size(); + _payload_buffer.resize(idx + TSPacket::kSize); + memcpy(&_payload_buffer[idx], tspacket.data(), TSPacket::kSize); + return; + } + + // We are free to write the packet, but if we have buffered packet[s] + // we have to write them first... + if (!_payload_buffer.empty()) + { + if (ringBuffer) + ringBuffer->Write(&_payload_buffer[0], _payload_buffer.size()); + _payload_buffer.clear(); + } } if (ringBuffer) @@ -393,12 +390,11 @@ static const uint frameRateMap[16] = { */ bool DTVRecorder::FindMPEG2Keyframes(const TSPacket* tspacket) { - bool haveBufferedData = !_payload_buffer.empty(); if (!tspacket->HasPayload()) // no payload to scan - return !haveBufferedData; + return _first_keyframe >= 0; if (!ringBuffer) - return !haveBufferedData; + return _first_keyframe >= 0; // if packet contains start of PES packet, start // looking for first byte of MPEG start code (3 bytes 0 0 1) @@ -526,15 +522,34 @@ bool DTVRecorder::FindMPEG2Keyframes(const TSPacket* tspacket) if (hasKeyFrame) { + LOG(VB_RECORD, LOG_DEBUG, LOC + QString + ("Keyframe @ %1 + %2 = %3") + .arg(ringBuffer->GetWritePosition()) + .arg(_payload_buffer.size()) + .arg(ringBuffer->GetWritePosition() + _payload_buffer.size())); + _last_keyframe_seen = _frames_seen_count; - HandleKeyframe(_frames_written_count, TSPacket::kSize); + HandleKeyframe(0); } if (hasFrame) { + LOG(VB_RECORD, LOG_DEBUG, LOC + QString + ("Frame @ %1 + %2 = %3") + .arg(ringBuffer->GetWritePosition()) + .arg(_payload_buffer.size()) + .arg(ringBuffer->GetWritePosition() + _payload_buffer.size())); + + _buffer_packets = false; // We now know if it is a keyframe, or not _frames_seen_count++; - if (!_wait_for_keyframe_option || _first_keyframe>=0) + if (!_wait_for_keyframe_option || _first_keyframe >= 0) _frames_written_count++; + else + { + /* Found a frame that is not a keyframe, and we want to + * start on a keyframe */ + _payload_buffer.clear(); + } } if ((aspectRatio > 0) && (aspectRatio != m_videoAspect)) @@ -558,7 +573,7 @@ bool DTVRecorder::FindMPEG2Keyframes(const TSPacket* tspacket) FrameRateChange(frameRate, _frames_written_count); } - return hasKeyFrame || (_payload_buffer.size() >= (188*50)); + return _first_keyframe >= 0; } void DTVRecorder::HandleTimestamps(int stream_id, int64_t pts, int64_t dts) @@ -652,7 +667,7 @@ bool DTVRecorder::FindAudioKeyframes(const TSPacket*) if (1 == (_frames_seen_count & 0x7)) { _last_keyframe_seen = _frames_seen_count; - HandleKeyframe(_frames_written_count); + HandleKeyframe(_payload_buffer.size()); hasKeyFrame = true; } @@ -680,7 +695,7 @@ bool DTVRecorder::FindOtherKeyframes(const TSPacket *tspacket) _frames_written_count++; _last_keyframe_seen = _frames_seen_count; - HandleKeyframe(_frames_written_count); + HandleKeyframe(_payload_buffer.size()); _has_written_other_keyframe = true; @@ -716,24 +731,22 @@ void DTVRecorder::SetNextRecording(const ProgramInfo *progInf, RingBuffer *rb) * \brief This save the current frame to the position maps * and handles ringbuffer switching. */ -void DTVRecorder::HandleKeyframe(uint64_t frameNum, int64_t extra) +void DTVRecorder::HandleKeyframe(int64_t extra) { if (!ringBuffer) return; -#if 0 - unsigned long long frameNum = _frames_written_count; -#endif + // Perform ringbuffer switch if needed. + CheckForRingBufferSwitch(); + uint64_t frameNum = _frames_written_count; _first_keyframe = (_first_keyframe < 0) ? frameNum : _first_keyframe; // Add key frame to position map positionMapLock.lock(); if (!positionMap.contains(frameNum)) { - long long startpos = ringBuffer->GetWritePosition(); - // FIXME: handle keyframes with start code spanning over two ts packets - startpos += _payload_buffer.size() - extra; + int64_t startpos = ringBuffer->GetWritePosition() + extra; // Don't put negative offsets into the database, they get munged into // MAX_INT64 - offset, which is an exceedingly large number, and @@ -745,9 +758,6 @@ void DTVRecorder::HandleKeyframe(uint64_t frameNum, int64_t extra) } } positionMapLock.unlock(); - - // Perform ringbuffer switch if needed. - CheckForRingBufferSwitch(); } /** \fn DTVRecorder::FindH264Keyframes(const TSPacket*) @@ -757,16 +767,15 @@ void DTVRecorder::HandleKeyframe(uint64_t frameNum, int64_t extra) */ bool DTVRecorder::FindH264Keyframes(const TSPacket *tspacket) { + if (!tspacket->HasPayload()) // no payload to scan + return _first_keyframe >= 0; + if (!ringBuffer) { LOG(VB_GENERAL, LOG_ERR, LOC + "FindH264Keyframes: No ringbuffer"); - return false; + return _first_keyframe >= 0; } - bool haveBufferedData = !_payload_buffer.empty(); - if (!tspacket->HasPayload()) // no payload to scan - return !haveBufferedData; - const bool payloadStart = tspacket->PayloadStart(); if (payloadStart) { @@ -785,7 +794,7 @@ bool DTVRecorder::FindH264Keyframes(const TSPacket *tspacket) // scan for PES packets and H.264 NAL units uint i = tspacket->AFCOffset(); - for (; i < TSPacket::kSize; i++) + for (; i < TSPacket::kSize; ++i) { // special handling required when a new PES packet begins if (payloadStart && !_pes_synced) @@ -853,10 +862,9 @@ bool DTVRecorder::FindH264Keyframes(const TSPacket *tspacket) // scan for a NAL unit start code - uint32_t bytes_used = m_h264_parser.addBytes( - tspacket->data() + i, TSPacket::kSize - i, - ringBuffer->GetWritePosition() + _payload_buffer.size() - ); + uint32_t bytes_used = m_h264_parser.addBytes + (tspacket->data() + i, TSPacket::kSize - i, + ringBuffer->GetWritePosition()); i += (bytes_used - 1); if (m_h264_parser.stateChanged()) @@ -874,19 +882,40 @@ bool DTVRecorder::FindH264Keyframes(const TSPacket *tspacket) frameRate = m_h264_parser.frameRate(); } } - } // for (; i < TSPacket::kSize; i++) + } // for (; i < TSPacket::kSize; ++i) if (hasKeyFrame) { + LOG(VB_RECORD, LOG_DEBUG, LOC + QString + ("Keyframe @ %1 + %2 = %3 AU %4") + .arg(ringBuffer->GetWritePosition()) + .arg(_payload_buffer.size()) + .arg(ringBuffer->GetWritePosition() + _payload_buffer.size()) + .arg(m_h264_parser.keyframeAUstreamOffset())); + _last_keyframe_seen = _frames_seen_count; HandleH264Keyframe(); } if (hasFrame) { + LOG(VB_RECORD, LOG_DEBUG, LOC + QString + ("Frame @ %1 + %2 = %3 AU %4") + .arg(ringBuffer->GetWritePosition()) + .arg(_payload_buffer.size()) + .arg(ringBuffer->GetWritePosition() + _payload_buffer.size()) + .arg(m_h264_parser.keyframeAUstreamOffset())); + + _buffer_packets = false; // We now know if this is a keyframe _frames_seen_count++; if (!_wait_for_keyframe_option || _first_keyframe >= 0) _frames_written_count++; + else + { + /* Found a frame that is not a keyframe, and we want to + * start on a keyframe */ + _payload_buffer.clear(); + } } if ((aspectRatio > 0) && (aspectRatio != m_videoAspect)) @@ -904,7 +933,6 @@ bool DTVRecorder::FindH264Keyframes(const TSPacket *tspacket) if (frameRate != 0 && frameRate != m_frameRate) { - LOG(VB_RECORD, LOG_INFO, LOC + QString("FindH264Keyframes: timescale: %1, tick: %2, framerate: %3") .arg( m_h264_parser.GetTimeScale() ) @@ -914,7 +942,7 @@ bool DTVRecorder::FindH264Keyframes(const TSPacket *tspacket) FrameRateChange(frameRate, _frames_written_count); } - return hasKeyFrame || (_payload_buffer.size() >= (188*50)); + return _seen_sps; } /** \fn DTVRecorder::HandleH264Keyframe(void) @@ -923,21 +951,28 @@ bool DTVRecorder::FindH264Keyframes(const TSPacket *tspacket) */ void DTVRecorder::HandleH264Keyframe(void) { - unsigned long long frameNum = _frames_written_count; + // Perform ringbuffer switch if needed. + CheckForRingBufferSwitch(); - _first_keyframe = (_first_keyframe < 0) ? frameNum : _first_keyframe; + uint64_t startpos; + uint64_t frameNum = _frames_written_count; + + if (_first_keyframe < 0) + { + _first_keyframe = frameNum; + startpos = 0; + } + else + startpos = m_h264_parser.keyframeAUstreamOffset(); // Add key frame to position map positionMapLock.lock(); if (!positionMap.contains(frameNum)) { - positionMapDelta[frameNum] = m_h264_parser.keyframeAUstreamOffset(); - positionMap[frameNum] = m_h264_parser.keyframeAUstreamOffset(); + positionMapDelta[frameNum] = startpos; + positionMap[frameNum] = startpos; } positionMapLock.unlock(); - - // Perform ringbuffer switch if needed. - CheckForRingBufferSwitch(); } void DTVRecorder::FindPSKeyFrames(const uint8_t *buffer, uint len) @@ -1057,7 +1092,7 @@ void DTVRecorder::FindPSKeyFrames(const uint8_t *buffer, uint len) if (hasKeyFrame) { _last_keyframe_seen = _frames_seen_count; - HandleKeyframe(_frames_written_count, bufptr - bufstart); + HandleKeyframe(_payload_buffer.size() - (bufptr - bufstart)); } if ((aspectRatio > 0) && (aspectRatio != m_videoAspect)) @@ -1141,8 +1176,9 @@ void DTVRecorder::HandlePAT(const ProgramAssociationTable *_pat) if (!pmtpid) { - LOG(VB_RECORD, LOG_ERR, LOC + "SetPAT(): " - "Ignoring PAT not containing our desired program..."); + LOG(VB_RECORD, LOG_ERR, LOC + + QString("SetPAT(): Ignoring PAT not containing our desired " + "program (%1)...").arg(progNum)); return; } @@ -1154,7 +1190,7 @@ void DTVRecorder::HandlePAT(const ProgramAssociationTable *_pat) delete oldpat; // Listen for the other PMTs for faster channel switching - for (uint i = 0; _input_pat && (i < _input_pat->ProgramCount()); i++) + for (uint i = 0; _input_pat && (i < _input_pat->ProgramCount()); ++i) { uint pmt_pid = _input_pat->ProgramPID(i); if (!_stream_data->IsListeningPID(pmt_pid)) @@ -1175,7 +1211,7 @@ void DTVRecorder::HandlePMT(uint progNum, const ProgramMapTable *_pmt) QString sistandard = GetSIStandard(); bool has_no_av = true; - for (uint i = 0; i < _input_pmt->StreamCount() && has_no_av; i++) + for (uint i = 0; i < _input_pmt->StreamCount() && has_no_av; ++i) { has_no_av &= !_input_pmt->IsVideo(i, sistandard); has_no_av &= !_input_pmt->IsAudio(i, sistandard); @@ -1187,7 +1223,8 @@ void DTVRecorder::HandlePMT(uint progNum, const ProgramMapTable *_pmt) } } -void DTVRecorder::HandleSingleProgramPAT(ProgramAssociationTable *pat) +void DTVRecorder::HandleSingleProgramPAT(ProgramAssociationTable *pat, + bool insert) { if (!pat) { @@ -1202,11 +1239,11 @@ void DTVRecorder::HandleSingleProgramPAT(ProgramAssociationTable *pat) pat->tsheader()->SetContinuityCounter(next_cc); pat->GetAsTSPackets(_scratch, next_cc); - for (uint i = 0; i < _scratch.size(); i++) - DTVRecorder::BufferedWrite(_scratch[i]); + for (uint i = 0; i < _scratch.size(); ++i) + DTVRecorder::BufferedWrite(_scratch[i], insert); } -void DTVRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) +void DTVRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt, bool insert) { if (!pmt) { @@ -1215,7 +1252,7 @@ void DTVRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) } // collect stream types for H.264 (MPEG-4 AVC) keyframe detection - for (uint i = 0; i < pmt->StreamCount(); i++) + for (uint i = 0; i < pmt->StreamCount(); ++i) _stream_id[pmt->StreamPID(i)] = pmt->StreamType(i); if (!ringBuffer) @@ -1225,8 +1262,8 @@ void DTVRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) pmt->tsheader()->SetContinuityCounter(next_cc); pmt->GetAsTSPackets(_scratch, next_cc); - for (uint i = 0; i < _scratch.size(); i++) - DTVRecorder::BufferedWrite(_scratch[i]); + for (uint i = 0; i < _scratch.size(); ++i) + DTVRecorder::BufferedWrite(_scratch[i], insert); } bool DTVRecorder::ProcessTSPacket(const TSPacket &tspacket) @@ -1252,7 +1289,8 @@ bool DTVRecorder::ProcessTSPacket(const TSPacket &tspacket) // Only create fake keyframe[s] if there are no audio/video streams if (_input_pmt && _has_no_av) { - _buffer_packets = !FindOtherKeyframes(&tspacket); + FindOtherKeyframes(&tspacket); + _buffer_packets = false; } else { @@ -1276,17 +1314,20 @@ bool DTVRecorder::ProcessVideoTSPacket(const TSPacket &tspacket) uint streamType = _stream_id[tspacket.PID()]; - // Check for keyframes and count frames - if (streamType == StreamID::H264Video) + if (tspacket.HasPayload() && tspacket.PayloadStart()) { - _buffer_packets = !FindH264Keyframes(&tspacket); - if (_wait_for_keyframe_option && !_seen_sps) - return true; + // buffer packets until we know if this is a keyframe + _buffer_packets = true; } + + // Check for keyframes and count frames + if (streamType == StreamID::H264Video) + FindH264Keyframes(&tspacket); + else if (streamType != 0) + FindMPEG2Keyframes(&tspacket); else - { - _buffer_packets = !FindMPEG2Keyframes(&tspacket); - } + LOG(VB_RECORD, LOG_ERR, LOC + + "ProcessVideoTSPacket: unknown stream type!"); return ProcessAVTSPacket(tspacket); } @@ -1296,13 +1337,23 @@ bool DTVRecorder::ProcessAudioTSPacket(const TSPacket &tspacket) if (!ringBuffer) return true; - _buffer_packets = !FindAudioKeyframes(&tspacket); + if (tspacket.HasPayload() && tspacket.PayloadStart()) + { + // buffer packets until we know if this is a keyframe + _buffer_packets = true; + } + + FindAudioKeyframes(&tspacket); return ProcessAVTSPacket(tspacket); } /// Common code for processing either audio or video packets bool DTVRecorder::ProcessAVTSPacket(const TSPacket &tspacket) { + // Sync recording start to first keyframe + if (!_buffer_packets && _wait_for_keyframe_option && _first_keyframe < 0) + return true; + const uint pid = tspacket.PID(); if (pid != 0x1fff) @@ -1320,10 +1371,6 @@ bool DTVRecorder::ProcessAVTSPacket(const TSPacket &tspacket) .arg(erate,5,'f',2)); } - // Sync recording start to first keyframe - if (_wait_for_keyframe_option && _first_keyframe < 0) - return true; - // Sync streams to the first Payload Unit Start Indicator // _after_ first keyframe iff _wait_for_keyframe_option is true if (!(_pid_status[pid] & kPayloadStartSeen) && tspacket.HasPayload()) diff --git a/mythtv/libs/libmythtv/dtvrecorder.h b/mythtv/libs/libmythtv/dtvrecorder.h index e2749bf0944..5ecbf1bfcd7 100644 --- a/mythtv/libs/libmythtv/dtvrecorder.h +++ b/mythtv/libs/libmythtv/dtvrecorder.h @@ -66,8 +66,8 @@ class DTVRecorder : void HandleEncryptionStatus(uint /*pnum*/, bool /*encrypted*/) { } // MPEG Single Program Stream Listener - void HandleSingleProgramPAT(ProgramAssociationTable *pat); - void HandleSingleProgramPMT(ProgramMapTable *pmt); + void HandleSingleProgramPAT(ProgramAssociationTable *pat, bool insert); + void HandleSingleProgramPMT(ProgramMapTable *pmt, bool insert); // ATSC Main void HandleSTT(const SystemTimeTable*) { UpdateCAMTimeOffset(); } @@ -93,10 +93,10 @@ class DTVRecorder : void FinishRecording(void); void ResetForNewFile(void); - void HandleKeyframe(uint64_t frameNum, int64_t extra = 0); + void HandleKeyframe(int64_t extra); void HandleTimestamps(int stream_id, int64_t pts, int64_t dts); - void BufferedWrite(const TSPacket &tspacket); + void BufferedWrite(const TSPacket &tspacket, bool insert = false); // MPEG TS "audio only" support bool FindAudioKeyframes(const TSPacket *tspacket); diff --git a/mythtv/libs/libmythtv/dvbrecorder.cpp b/mythtv/libs/libmythtv/dvbrecorder.cpp index c1b66d59ce5..abd322a50e5 100644 --- a/mythtv/libs/libmythtv/dvbrecorder.cpp +++ b/mythtv/libs/libmythtv/dvbrecorder.cpp @@ -72,6 +72,13 @@ void DVBRecorder::Close(void) LOG(VB_RECORD, LOG_INFO, LOC + "Close() -- end"); } +void DVBRecorder::StartNewFile(void) +{ + // Make sure the first things in the file are a PAT & PMT + HandleSingleProgramPAT(_stream_data->PATSingleProgram(), true); + HandleSingleProgramPMT(_stream_data->PMTSingleProgram(), true); +} + void DVBRecorder::run(void) { if (!Open()) @@ -92,12 +99,7 @@ void DVBRecorder::run(void) if (_channel && (_channel->GetSIStandard() == "dvb")) _stream_data->AddListeningPID(DVB_TDT_PID); - // Make sure the first things in the file are a PAT & PMT - bool tmp = _wait_for_keyframe_option; - _wait_for_keyframe_option = false; - HandleSingleProgramPAT(_stream_data->PATSingleProgram()); - HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); - _wait_for_keyframe_option = tmp; + StartNewFile(); _stream_data->AddAVListener(this); _stream_data->AddWritingListener(this); diff --git a/mythtv/libs/libmythtv/dvbrecorder.h b/mythtv/libs/libmythtv/dvbrecorder.h index eb0b5f80ddb..e82c2fec1d5 100644 --- a/mythtv/libs/libmythtv/dvbrecorder.h +++ b/mythtv/libs/libmythtv/dvbrecorder.h @@ -28,6 +28,7 @@ class DVBRecorder : public DTVRecorder bool Open(void); bool IsOpen(void) const; void Close(void); + void StartNewFile(void); private: bool PauseAndWait(int timeout = 100); diff --git a/mythtv/libs/libmythtv/hdhrrecorder.cpp b/mythtv/libs/libmythtv/hdhrrecorder.cpp index 63e128bcf45..08422dd05ea 100644 --- a/mythtv/libs/libmythtv/hdhrrecorder.cpp +++ b/mythtv/libs/libmythtv/hdhrrecorder.cpp @@ -47,6 +47,13 @@ void HDHRRecorder::Close(void) LOG(VB_RECORD, LOG_INFO, LOC + "Close() -- end"); } +void HDHRRecorder::StartNewFile(void) +{ + // Make sure the first things in the file are a PAT & PMT + HandleSingleProgramPAT(_stream_data->PATSingleProgram(), true); + HandleSingleProgramPMT(_stream_data->PMTSingleProgram(), true); +} + void HDHRRecorder::run(void) { LOG(VB_RECORD, LOG_INFO, LOC + "run -- begin"); @@ -66,12 +73,7 @@ void HDHRRecorder::run(void) recordingWait.wakeAll(); } - // Make sure the first things in the file are a PAT & PMT - bool tmp = _wait_for_keyframe_option; - _wait_for_keyframe_option = false; - HandleSingleProgramPAT(_stream_data->PATSingleProgram()); - HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); - _wait_for_keyframe_option = tmp; + StartNewFile(); _stream_data->AddAVListener(this); _stream_data->AddWritingListener(this); diff --git a/mythtv/libs/libmythtv/hdhrrecorder.h b/mythtv/libs/libmythtv/hdhrrecorder.h index 44b3fc18305..7523e370f90 100644 --- a/mythtv/libs/libmythtv/hdhrrecorder.h +++ b/mythtv/libs/libmythtv/hdhrrecorder.h @@ -26,6 +26,7 @@ class HDHRRecorder : public DTVRecorder bool Open(void); bool IsOpen(void) const { return _stream_handler; } void Close(void); + void StartNewFile(void); QString GetSIStandard(void) const; diff --git a/mythtv/libs/libmythtv/mpeg/mpegstreamdata.cpp b/mythtv/libs/libmythtv/mpeg/mpegstreamdata.cpp index 43bcbe86811..d8197769ffb 100644 --- a/mythtv/libs/libmythtv/mpeg/mpegstreamdata.cpp +++ b/mythtv/libs/libmythtv/mpeg/mpegstreamdata.cpp @@ -836,7 +836,7 @@ void MPEGStreamData::ProcessPAT(const ProgramAssociationTable *pat) QMutexLocker locker(&_listener_lock); ProgramAssociationTable *pat_sp = PATSingleProgram(); for (uint i = 0; i < _mpeg_sp_listeners.size(); i++) - _mpeg_sp_listeners[i]->HandleSingleProgramPAT(pat_sp); + _mpeg_sp_listeners[i]->HandleSingleProgramPAT(pat_sp, false); } } @@ -871,7 +871,7 @@ void MPEGStreamData::ProcessPMT(const ProgramMapTable *pmt) QMutexLocker locker(&_listener_lock); ProgramMapTable *pmt_sp = PMTSingleProgram(); for (uint i = 0; i < _mpeg_sp_listeners.size(); i++) - _mpeg_sp_listeners[i]->HandleSingleProgramPMT(pmt_sp); + _mpeg_sp_listeners[i]->HandleSingleProgramPMT(pmt_sp, false); } } @@ -982,7 +982,7 @@ void MPEGStreamData::HandleTSTables(const TSPacket* tspacket) QMutexLocker locker(&_listener_lock); ProgramAssociationTable *pat_sp = PATSingleProgram(); for (uint i = 0; i < _mpeg_sp_listeners.size(); i++) - _mpeg_sp_listeners[i]->HandleSingleProgramPAT(pat_sp); + _mpeg_sp_listeners[i]->HandleSingleProgramPAT(pat_sp, false); } if (TableID::PMT == psip->TableID() && tspacket->PID() == _pid_pmt_single_program) @@ -990,7 +990,7 @@ void MPEGStreamData::HandleTSTables(const TSPacket* tspacket) QMutexLocker locker(&_listener_lock); ProgramMapTable *pmt_sp = PMTSingleProgram(); for (uint i = 0; i < _mpeg_sp_listeners.size(); i++) - _mpeg_sp_listeners[i]->HandleSingleProgramPMT(pmt_sp); + _mpeg_sp_listeners[i]->HandleSingleProgramPMT(pmt_sp, false); } DONE_WITH_PSIP_PACKET(); // already parsed this table, toss it. } diff --git a/mythtv/libs/libmythtv/mpeg/streamlisteners.h b/mythtv/libs/libmythtv/mpeg/streamlisteners.h index bc9cb2d487e..9412d5d8dc3 100644 --- a/mythtv/libs/libmythtv/mpeg/streamlisteners.h +++ b/mythtv/libs/libmythtv/mpeg/streamlisteners.h @@ -95,8 +95,9 @@ class MPEGSingleProgramStreamListener protected: virtual ~MPEGSingleProgramStreamListener() {} public: - virtual void HandleSingleProgramPAT(ProgramAssociationTable*) = 0; - virtual void HandleSingleProgramPMT(ProgramMapTable*) = 0; + virtual void HandleSingleProgramPAT(ProgramAssociationTable*, + bool insert) = 0; + virtual void HandleSingleProgramPMT(ProgramMapTable*, bool insert) = 0; }; class ATSCMainStreamListener diff --git a/mythtv/libs/libmythtv/mpegrecorder.cpp b/mythtv/libs/libmythtv/mpegrecorder.cpp index ebb6e1a2d88..66747f2e492 100644 --- a/mythtv/libs/libmythtv/mpegrecorder.cpp +++ b/mythtv/libs/libmythtv/mpegrecorder.cpp @@ -929,9 +929,8 @@ void MpegRecorder::run(void) _stream_data->AddWritingListener(this); // Make sure the first things in the file are a PAT & PMT - _wait_for_keyframe_option = false; - HandleSingleProgramPAT(_stream_data->PATSingleProgram()); - HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); + HandleSingleProgramPAT(_stream_data->PATSingleProgram(), true); + HandleSingleProgramPMT(_stream_data->PMTSingleProgram(), true); _wait_for_keyframe_option = true; } @@ -1256,8 +1255,8 @@ void MpegRecorder::RestartEncoding(void) _stream_data->PMTSingleProgram()) { _wait_for_keyframe_option = false; - HandleSingleProgramPAT(_stream_data->PATSingleProgram()); - HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); + HandleSingleProgramPAT(_stream_data->PATSingleProgram(), false); + HandleSingleProgramPMT(_stream_data->PMTSingleProgram(), false); } if (driver == "hdpvr") // HD-PVR will sometimes reset to defaults