From c900ea876059532737ad07fe1f634ea25cd3fa72 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Wed, 17 Jan 2024 14:43:33 -0700 Subject: [PATCH 01/18] feat: initial CC changes for mpegts outputs --- .../media/formats/mp2t/continuity_counter.cc | 21 +++++++++---- .../media/formats/mp2t/continuity_counter.h | 8 +++-- .../formats/mp2t/program_map_table_writer.cc | 30 +++++++++++++++---- .../formats/mp2t/program_map_table_writer.h | 7 ++++- packager/media/formats/mp2t/ts_segmenter.cc | 27 +++++++++-------- packager/media/formats/mp2t/ts_writer.cc | 10 +++++-- packager/media/formats/mp2t/ts_writer.h | 9 +++++- 7 files changed, 80 insertions(+), 32 deletions(-) diff --git a/packager/media/formats/mp2t/continuity_counter.cc b/packager/media/formats/mp2t/continuity_counter.cc index d13dac8c7c5..48f71af6bac 100644 --- a/packager/media/formats/mp2t/continuity_counter.cc +++ b/packager/media/formats/mp2t/continuity_counter.cc @@ -10,16 +10,25 @@ namespace shaka { namespace media { namespace mp2t { -ContinuityCounter::ContinuityCounter() {} -ContinuityCounter::~ContinuityCounter() {} +ContinuityCounter::ContinuityCounter() : ContinuityCounter(0) {} +ContinuityCounter::ContinuityCounter(unsigned int segment_number) + : counter_(segment_number & 0xF) {} -int ContinuityCounter::GetNext() { - int ret = counter_; - ++counter_; - counter_ %= 16; +ContinuityCounter::~ContinuityCounter() = default; + +unsigned int ContinuityCounter::GetNext() { + // int ret = counter_; + // ++counter_; + // counter_ %= 16; + // return ret; + unsigned int ret = ((counter_++) & 0x0F); return ret; } +unsigned int ContinuityCounter::GetContinuityCounter() const { + return counter_; +} + } // namespace mp2t } // namespace media } // namespace shaka diff --git a/packager/media/formats/mp2t/continuity_counter.h b/packager/media/formats/mp2t/continuity_counter.h index c824cb76dc5..54c55e5eec7 100644 --- a/packager/media/formats/mp2t/continuity_counter.h +++ b/packager/media/formats/mp2t/continuity_counter.h @@ -15,16 +15,20 @@ namespace mp2t { class ContinuityCounter { public: + ContinuityCounter(unsigned int segment_number); ContinuityCounter(); ~ContinuityCounter(); /// As specified by the spec, this starts from 0 and is incremented by 1 until /// it wraps back to 0 when it reaches 16. /// @return counter value. - int GetNext(); + unsigned int GetNext(); + + /// @return the current value of the continuity counter. + [[nodiscard]] unsigned int GetContinuityCounter() const; private: - int counter_ = 0; + unsigned int counter_; DISALLOW_COPY_AND_ASSIGN(ContinuityCounter); }; diff --git a/packager/media/formats/mp2t/program_map_table_writer.cc b/packager/media/formats/mp2t/program_map_table_writer.cc index a2a072d19df..3deeac9dc10 100644 --- a/packager/media/formats/mp2t/program_map_table_writer.cc +++ b/packager/media/formats/mp2t/program_map_table_writer.cc @@ -30,7 +30,7 @@ const int kVersion1 = 1; // Values for current_next_indicator. const int kCurrent = 1; -const int kNext= 0; +const int kNext = 0; // Program number is 16 bits but 8 bits is sufficient. const uint8_t kProgramNumber = 0x01; @@ -172,8 +172,7 @@ void WritePmtWithParameters(uint8_t stream_type, pmt_body.AppendInt(kProgramNumber); // resevered bits then version and current_next_indicator. pmt_body.AppendInt( - static_cast(0xC0 | - static_cast(version) << 1 | + static_cast(0xC0 | static_cast(version) << 1 | static_cast(current_next_indicator))); // section number. pmt_body.AppendInt(static_cast(0x00)); @@ -215,7 +214,15 @@ void WritePmtWithParameters(uint8_t stream_type, } // namespace -ProgramMapTableWriter::ProgramMapTableWriter(Codec codec) : codec_(codec) {} +ProgramMapTableWriter::ProgramMapTableWriter(Codec codec) + : ProgramMapTableWriter(codec, 0) {} + +// use segment number as continuity counter as PMT types have single packets, +// therefore, using the segment number as the CC will be continuous across +// segments +ProgramMapTableWriter::ProgramMapTableWriter(Codec codec, + unsigned int segment_number) + : codec_(codec), continuity_counter_(ContinuityCounter(segment_number)) {} bool ProgramMapTableWriter::EncryptedSegmentPmt(BufferWriter* writer) { if (encrypted_pmt_.Size() == 0) { @@ -288,7 +295,12 @@ bool ProgramMapTableWriter::ClearSegmentPmt(BufferWriter* writer) { } VideoProgramMapTableWriter::VideoProgramMapTableWriter(Codec codec) - : ProgramMapTableWriter(codec) {} + : VideoProgramMapTableWriter(codec, 0) {} + +VideoProgramMapTableWriter::VideoProgramMapTableWriter( + Codec codec, + unsigned int segment_number) + : ProgramMapTableWriter(codec, segment_number) {} bool VideoProgramMapTableWriter::WriteDescriptors( BufferWriter* descriptors) const { @@ -308,7 +320,13 @@ bool VideoProgramMapTableWriter::WriteDescriptors( AudioProgramMapTableWriter::AudioProgramMapTableWriter( Codec codec, const std::vector& audio_specific_config) - : ProgramMapTableWriter(codec), + : AudioProgramMapTableWriter(codec, audio_specific_config, 0) {} + +AudioProgramMapTableWriter::AudioProgramMapTableWriter( + Codec codec, + const std::vector& audio_specific_config, + unsigned int segment_number) + : ProgramMapTableWriter(codec, segment_number), audio_specific_config_(audio_specific_config) { DCHECK(!audio_specific_config.empty()); } diff --git a/packager/media/formats/mp2t/program_map_table_writer.h b/packager/media/formats/mp2t/program_map_table_writer.h index cc30e1f0ff3..2a6e54cb6a1 100644 --- a/packager/media/formats/mp2t/program_map_table_writer.h +++ b/packager/media/formats/mp2t/program_map_table_writer.h @@ -25,7 +25,8 @@ namespace mp2t { /// Puts PMT into TS packets and writes them to buffer. class ProgramMapTableWriter { public: - explicit ProgramMapTableWriter(Codec codec); + ProgramMapTableWriter(Codec codec); + ProgramMapTableWriter(Codec codec, unsigned int segment_number); virtual ~ProgramMapTableWriter() = default; /// Writes TS packets with PMT for encrypted segments. @@ -64,6 +65,7 @@ class ProgramMapTableWriter { class VideoProgramMapTableWriter : public ProgramMapTableWriter { public: explicit VideoProgramMapTableWriter(Codec codec); + VideoProgramMapTableWriter(Codec codec, unsigned int segment_number); ~VideoProgramMapTableWriter() override = default; private: @@ -79,6 +81,9 @@ class AudioProgramMapTableWriter : public ProgramMapTableWriter { public: AudioProgramMapTableWriter(Codec codec, const std::vector& audio_specific_config); + AudioProgramMapTableWriter(Codec codec, + const std::vector& audio_specific_config, + unsigned int segment_number); ~AudioProgramMapTableWriter() override = default; private: diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index f2daaeafdef..9002d807e1f 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -76,6 +76,7 @@ Status TsSegmenter::Finalize() { Status TsSegmenter::AddSample(const MediaSample& sample) { if (!ts_writer_) { + uint32_t segment_number = muxer_options_.mp4_params.sequence_number; std::unique_ptr pmt_writer; if (codec_ == kCodecAC3) { // https://goo.gl/N7Tvqi MPEG-2 Stream Encryption Format for HTTP Live @@ -91,15 +92,16 @@ Status TsSegmenter::AddSample(const MediaSample& sample) { } const std::vector setup_data(sample.data(), sample.data() + kSetupDataSize); - pmt_writer.reset(new AudioProgramMapTableWriter(codec_, setup_data)); - } else if (IsAudioCodec(codec_)) { pmt_writer.reset( - new AudioProgramMapTableWriter(codec_, audio_codec_config_)); + new AudioProgramMapTableWriter(codec_, setup_data, segment_number)); + } else if (IsAudioCodec(codec_)) { + pmt_writer.reset(new AudioProgramMapTableWriter( + codec_, audio_codec_config_, segment_number)); } else { DCHECK(IsVideoCodec(codec_)); - pmt_writer.reset(new VideoProgramMapTableWriter(codec_)); + pmt_writer.reset(new VideoProgramMapTableWriter(codec_, segment_number)); } - ts_writer_.reset(new TsWriter(std::move(pmt_writer))); + ts_writer_.reset(new TsWriter(std::move(pmt_writer), segment_number)); } if (sample.is_encrypted()) @@ -148,7 +150,6 @@ Status TsSegmenter::WritePesPackets() { return status; if (listener_ && IsVideoCodec(codec_) && pes_packet->is_key_frame()) { - uint64_t start_pos = segment_buffer_.Size(); const int64_t timestamp = pes_packet->pts(); if (!ts_writer_->AddPesPacket(std::move(pes_packet), &segment_buffer_)) @@ -178,8 +179,8 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) { if (!segment_started_) return Status::OK; std::string segment_path = - GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_, - segment_number_++, muxer_options_.bandwidth); + GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_, + segment_number_++, muxer_options_.bandwidth); const int64_t file_size = segment_buffer_.Size(); std::unique_ptr segment_file; @@ -195,14 +196,14 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) { return Status( error::FILE_FAILURE, "Cannot close file " + segment_path + - ", possibly file permission issue or running out of disk space."); + ", possibly file permission issue or running out of disk space."); } if (listener_) { - listener_->OnNewSegment(segment_path, - start_timestamp * timescale_scale_ + - transport_stream_timestamp_offset_, - duration * timescale_scale_, file_size); + listener_->OnNewSegment( + segment_path, + start_timestamp * timescale_scale_ + transport_stream_timestamp_offset_, + duration * timescale_scale_, file_size); } segment_started_ = false; diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index 78457c64fb8..daa91f658f5 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -162,7 +162,12 @@ bool WritePesToBuffer(const PesPacket& pes, } // namespace TsWriter::TsWriter(std::unique_ptr pmt_writer) - : pmt_writer_(std::move(pmt_writer)) {} + : TsWriter(std::move(pmt_writer), 0) {} + +TsWriter::TsWriter(std::unique_ptr pmt_writer, + unsigned int segment_number) + : pat_continuity_counter_(ContinuityCounter(segment_number)), + pmt_writer_(std::move(pmt_writer)) {} TsWriter::~TsWriter() {} @@ -188,8 +193,7 @@ void TsWriter::SignalEncrypted() { } bool TsWriter::AddPesPacket(std::unique_ptr pes_packet, - BufferWriter* buffer) { - + BufferWriter* buffer) { if (!WritePesToBuffer(*pes_packet, &elementary_stream_continuity_counter_, buffer)) { LOG(ERROR) << "Failed to write pes to buffer."; diff --git a/packager/media/formats/mp2t/ts_writer.h b/packager/media/formats/mp2t/ts_writer.h index 5d5541de028..ae8ba690c0e 100644 --- a/packager/media/formats/mp2t/ts_writer.h +++ b/packager/media/formats/mp2t/ts_writer.h @@ -30,7 +30,14 @@ class ProgramMapTableWriter; /// the data to file. This also creates PSI from StreamInfo. class TsWriter { public: - explicit TsWriter(std::unique_ptr pmt_writer); + TsWriter(std::unique_ptr pmt_writer); + + /// Create TsWriter with segment_number + /// @param pmt_writer the writes PMT into ts packets + //// @param segment_number is used to set the continuity counter for PAT + /// packets. + TsWriter(std::unique_ptr pmt_writer, + unsigned int segment_number); virtual ~TsWriter(); /// This will fail if the current segment is not finalized. From 5447cdb312e5f39b4da1207c9ec496f0ee478c70 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Sat, 20 Jan 2024 15:18:28 -0700 Subject: [PATCH 02/18] feat: include unit test to check CC --- packager/live_packager_test.cc | 84 ++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/packager/live_packager_test.cc b/packager/live_packager_test.cc index b47ea072980..a4f0fd9b3af 100644 --- a/packager/live_packager_test.cc +++ b/packager/live_packager_test.cc @@ -17,10 +17,17 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -427,6 +434,7 @@ TEST(GeneratePSSHData, FailsOnInvalidInput) { class LivePackagerBaseTest : public ::testing::Test { public: void SetUp() override { + mp2t_parser_ = std::make_unique(); key_.assign(kKey, kKey + std::size(kKey)); iv_.assign(kIv, kIv + std::size(kIv)); key_id_.assign(kKeyId, kKeyId + std::size(kKeyId)); @@ -457,6 +465,8 @@ class LivePackagerBaseTest : public ::testing::Test { std::vector key_; std::vector iv_; std::vector key_id_; + + std::unique_ptr mp2t_parser_; }; TEST_F(LivePackagerBaseTest, InitSegmentOnly) { @@ -597,6 +607,80 @@ TEST_F(LivePackagerBaseTest, EncryptionFailure) { } } +TEST_F(LivePackagerBaseTest, CheckContinutityCounter) { + std::vector init_segment_buffer = ReadTestDataFile("input/init.mp4"); + ASSERT_FALSE(init_segment_buffer.empty()); + + media::ByteQueue ts_byte_queue; + + for (unsigned int i = 0; i < kNumSegments; i++) { + std::string segment_num = absl::StrFormat("input/%04d.m4s", i); + std::vector segment_buffer = ReadTestDataFile(segment_num); + ASSERT_FALSE(segment_buffer.empty()); + + SegmentData init_seg(init_segment_buffer.data(), + init_segment_buffer.size()); + SegmentData media_seg(segment_buffer.data(), segment_buffer.size()); + + FullSegmentBuffer out; + + LiveConfig live_config; + live_config.format = LiveConfig::OutputFormat::TS; + live_config.track_type = LiveConfig::TrackType::VIDEO; + live_config.protection_scheme = LiveConfig::EncryptionScheme::NONE; + live_config.segment_number = i; + + SetupLivePackagerConfig(live_config); + ASSERT_EQ(Status::OK, live_packager_->Package(init_seg, media_seg, out)); + ASSERT_GT(out.SegmentSize(), 0); + + ts_byte_queue.Push(out.SegmentData(), static_cast(out.SegmentSize())); + while (true) { + const uint8_t* ts_buffer; + int ts_buffer_size; + ts_byte_queue.Peek(&ts_buffer, &ts_buffer_size); + + if (ts_buffer_size < media::mp2t::TsPacket::kPacketSize) + break; + + // Synchronization. + int skipped_bytes = + media::mp2t::TsPacket::Sync(ts_buffer, ts_buffer_size); + if (skipped_bytes > 0) { + LOG(WARNING) << "Packet not aligned on a TS syncword:" + << " skipped_bytes=" << skipped_bytes; + ts_byte_queue.Pop(skipped_bytes); + continue; + } + + // Parse the TS header, skipping 1 byte if the header is invalid. + std::unique_ptr ts_packet( + media::mp2t::TsPacket::Parse(ts_buffer, ts_buffer_size)); + if (!ts_packet) { + LOG(WARNING) << "Error: invalid TS packet"; + ts_byte_queue.Pop(1); + continue; + } + + if (ts_packet->payload_unit_start_indicator() && + ts_packet->pid() == media::mp2t::TsSection::kPidPat) { + LOG(WARNING) << "Processing PID=" << ts_packet->pid() << " start_unit=" + << ts_packet->payload_unit_start_indicator() + << " continuity_counter=" + << ts_packet->continuity_counter(); + + // check the PAT continuity counter is in sync with the segment number. + EXPECT_EQ(ts_packet->continuity_counter(), live_config.segment_number); + } + + // Go to the next packet. + ts_byte_queue.Pop(media::mp2t::TsPacket::kPacketSize); + } + + ts_byte_queue.Reset(); + } +} + TEST_F(LivePackagerBaseTest, CustomMoofSequenceNumber) { std::vector init_segment_buffer = ReadTestDataFile("input/init.mp4"); ASSERT_FALSE(init_segment_buffer.empty()); From c0a038a8cd470d8c52af7f8e3cb0106d409128e9 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Mon, 22 Jan 2024 21:34:29 -0700 Subject: [PATCH 03/18] feat: update TS writer with null packet stuffing --- packager/live_packager.cc | 5 +- packager/live_packager_test.cc | 15 ++- .../media/formats/mp2t/continuity_counter.cc | 6 +- .../media/formats/mp2t/continuity_counter.h | 2 +- .../formats/mp2t/program_map_table_writer.cc | 107 +++++++----------- packager/media/formats/mp2t/ts_segmenter.cc | 3 +- packager/media/formats/mp2t/ts_writer.cc | 34 +++++- packager/media/formats/mp2t/ts_writer.h | 19 +++- 8 files changed, 105 insertions(+), 86 deletions(-) diff --git a/packager/live_packager.cc b/packager/live_packager.cc index 7e9291d46c2..893da8d63c3 100644 --- a/packager/live_packager.cc +++ b/packager/live_packager.cc @@ -34,8 +34,9 @@ namespace { using StreamDescriptors = std::vector; -// Shaka requires a non-zero value for segment duration otherwise it throws an error. -// For our use-case of packaging segments individually, this value has no effect. +// Shaka requires a non-zero value for segment duration otherwise it throws an +// error. For our use-case of packaging segments individually, this value has no +// effect. constexpr double DEFAULT_SEGMENT_DURATION = 5.0; const std::string INPUT_FNAME = "memory://input_file"; diff --git a/packager/live_packager_test.cc b/packager/live_packager_test.cc index 91412d35cd0..737e9bf36cf 100644 --- a/packager/live_packager_test.cc +++ b/packager/live_packager_test.cc @@ -661,16 +661,15 @@ TEST_F(LivePackagerBaseTest, CheckContinutityCounter) { } if (ts_packet->payload_unit_start_indicator() && - ts_packet->pid() == media::mp2t::TsSection::kPidPat) { - LOG(WARNING) << "Processing PID=" << ts_packet->pid() << " start_unit=" - << ts_packet->payload_unit_start_indicator() - << " continuity_counter=" - << ts_packet->continuity_counter(); - - // check the PAT continuity counter is in sync with the segment number. + (ts_packet->pid() == media::mp2t::TsSection::kPidPat || + ts_packet->pid() == 0x20)) { + LOG(INFO) << "Processing PID=" << ts_packet->pid() + << " start_unit=" << ts_packet->payload_unit_start_indicator() + << " continuity_counter=" << ts_packet->continuity_counter(); + // check the PAT (PID = 0x0) or PMT (PID = 0x20) continuity counter is + // in sync with the segment number. EXPECT_EQ(ts_packet->continuity_counter(), live_config.segment_number); } - // Go to the next packet. ts_byte_queue.Pop(media::mp2t::TsPacket::kPacketSize); } diff --git a/packager/media/formats/mp2t/continuity_counter.cc b/packager/media/formats/mp2t/continuity_counter.cc index 48f71af6bac..ef769f05304 100644 --- a/packager/media/formats/mp2t/continuity_counter.cc +++ b/packager/media/formats/mp2t/continuity_counter.cc @@ -17,15 +17,11 @@ ContinuityCounter::ContinuityCounter(unsigned int segment_number) ContinuityCounter::~ContinuityCounter() = default; unsigned int ContinuityCounter::GetNext() { - // int ret = counter_; - // ++counter_; - // counter_ %= 16; - // return ret; unsigned int ret = ((counter_++) & 0x0F); return ret; } -unsigned int ContinuityCounter::GetContinuityCounter() const { +unsigned int ContinuityCounter::GetCurrent() const { return counter_; } diff --git a/packager/media/formats/mp2t/continuity_counter.h b/packager/media/formats/mp2t/continuity_counter.h index 54c55e5eec7..05dbb516687 100644 --- a/packager/media/formats/mp2t/continuity_counter.h +++ b/packager/media/formats/mp2t/continuity_counter.h @@ -25,7 +25,7 @@ class ContinuityCounter { unsigned int GetNext(); /// @return the current value of the continuity counter. - [[nodiscard]] unsigned int GetContinuityCounter() const; + [[nodiscard]] unsigned int GetCurrent() const; private: unsigned int counter_; diff --git a/packager/media/formats/mp2t/program_map_table_writer.cc b/packager/media/formats/mp2t/program_map_table_writer.cc index 3deeac9dc10..b76f0e9e97c 100644 --- a/packager/media/formats/mp2t/program_map_table_writer.cc +++ b/packager/media/formats/mp2t/program_map_table_writer.cc @@ -38,69 +38,48 @@ const uint8_t kProgramMapTableId = 0x02; // Table for CRC32/MPEG2. const uint32_t kCrcTable[] = { - 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, - 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, - 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, - 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, - 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, - 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, - 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, - 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, - 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, - 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, - 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, - 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, - 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, - 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, - 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, - 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, - 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, - 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, - 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, - 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, - 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, - 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, - 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, - 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, - 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, - 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, - 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, - 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, - 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, - 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, - 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, - 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, - 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, - 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, - 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, - 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, - 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, - 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, - 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, - 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, - 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, - 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, - 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, - 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, - 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, - 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, - 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, - 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, - 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, - 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, - 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, - 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, - 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, - 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, - 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, - 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, - 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, - 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, - 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, - 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, - 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, - 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, - 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4, }; @@ -222,7 +201,7 @@ ProgramMapTableWriter::ProgramMapTableWriter(Codec codec) // segments ProgramMapTableWriter::ProgramMapTableWriter(Codec codec, unsigned int segment_number) - : codec_(codec), continuity_counter_(ContinuityCounter(segment_number)) {} + : codec_(codec), continuity_counter_(segment_number) {} bool ProgramMapTableWriter::EncryptedSegmentPmt(BufferWriter* writer) { if (encrypted_pmt_.Size() == 0) { diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index 9002d807e1f..c38bbfae3da 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -101,7 +101,8 @@ Status TsSegmenter::AddSample(const MediaSample& sample) { DCHECK(IsVideoCodec(codec_)); pmt_writer.reset(new VideoProgramMapTableWriter(codec_, segment_number)); } - ts_writer_.reset(new TsWriter(std::move(pmt_writer), segment_number)); + ts_writer_.reset( + new TsStuffingWriter(std::move(pmt_writer), segment_number)); } if (sample.is_encrypted()) diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index daa91f658f5..a4b83a2a539 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -15,6 +15,7 @@ #include #include #include +#include namespace shaka { namespace media { @@ -166,10 +167,13 @@ TsWriter::TsWriter(std::unique_ptr pmt_writer) TsWriter::TsWriter(std::unique_ptr pmt_writer, unsigned int segment_number) - : pat_continuity_counter_(ContinuityCounter(segment_number)), + : pat_continuity_counter_(segment_number), pmt_writer_(std::move(pmt_writer)) {} -TsWriter::~TsWriter() {} +TsStuffingWriter::TsStuffingWriter( + std::unique_ptr pmt_writer, + unsigned int segment_number) + : TsWriter(std::move(pmt_writer), segment_number) {} bool TsWriter::NewSegment(BufferWriter* buffer) { BufferWriter psi; @@ -204,6 +208,32 @@ bool TsWriter::AddPesPacket(std::unique_ptr pes_packet, return true; } +bool TsStuffingWriter::AddPesPacket(std::unique_ptr pes_packet, + BufferWriter* buffer) { + if (!WritePesToBuffer(*pes_packet, &elementary_stream_continuity_counter_, + buffer)) { + LOG(ERROR) << "Failed to write pes to buffer."; + return false; + } + + // We must end all ES packets at 0xf so that the next segment can start at + // 0x0. This can be done by stuffing null packets at the end of the segment + // for each elementary stream + do { + const int pid = ProgramMapTableWriter::kElementaryPid; + BufferWriter null_ts_packet_buffer; + null_ts_packet_buffer.AppendInt( + static_cast(TsSection::kPidNullPacket)); + WritePayloadToBufferWriter(null_ts_packet_buffer.Buffer(), + null_ts_packet_buffer.Size(), + !kPayloadUnitStartIndicator, pid, !kHasPcr, 0, + &elementary_stream_continuity_counter_, buffer); + } while ((elementary_stream_continuity_counter_.GetCurrent() & 0x0F) != 0); + + // No need to keep pes_packet around so not passing it anywhere. + return true; +} + } // namespace mp2t } // namespace media } // namespace shaka diff --git a/packager/media/formats/mp2t/ts_writer.h b/packager/media/formats/mp2t/ts_writer.h index ae8ba690c0e..975f8e2a006 100644 --- a/packager/media/formats/mp2t/ts_writer.h +++ b/packager/media/formats/mp2t/ts_writer.h @@ -38,7 +38,7 @@ class TsWriter { /// packets. TsWriter(std::unique_ptr pmt_writer, unsigned int segment_number); - virtual ~TsWriter(); + virtual ~TsWriter() = default; /// This will fail if the current segment is not finalized. /// @param buffer to write segment data. @@ -53,7 +53,11 @@ class TsWriter { /// @param pes_packet gets added to the writer. /// @param buffer to write pes packet. /// @return true on success, false otherwise. - virtual bool AddPesPacket(std::unique_ptr pes_packet, BufferWriter* buffer); + virtual bool AddPesPacket(std::unique_ptr pes_packet, + BufferWriter* buffer); + + protected: + ContinuityCounter elementary_stream_continuity_counter_; private: TsWriter(const TsWriter&) = delete; @@ -63,11 +67,20 @@ class TsWriter { bool encrypted_ = false; ContinuityCounter pat_continuity_counter_; - ContinuityCounter elementary_stream_continuity_counter_; std::unique_ptr pmt_writer_; }; +/// TsWriter to handle stuffing null TS Packets +class TsStuffingWriter : public TsWriter { + public: + TsStuffingWriter(std::unique_ptr pmt_writer, + unsigned int segment_number); + + bool AddPesPacket(std::unique_ptr pes_packet, + BufferWriter* buffer) override; +}; + } // namespace mp2t } // namespace media } // namespace shaka From 1aa8cdabdb59ba8e5af46e6e61d1c364e6a66ac0 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Tue, 23 Jan 2024 14:58:59 -0700 Subject: [PATCH 04/18] feat: improve CC unit test for PES packet checks --- packager/live_packager_test.cc | 10 ++++++++-- packager/media/formats/mp2t/continuity_counter.cc | 4 +++- packager/media/formats/mp2t/ts_writer.cc | 5 +++-- packager/media/formats/mp2t/ts_writer.h | 4 ++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packager/live_packager_test.cc b/packager/live_packager_test.cc index 737e9bf36cf..ef551c9dd17 100644 --- a/packager/live_packager_test.cc +++ b/packager/live_packager_test.cc @@ -570,7 +570,8 @@ TEST_F(LivePackagerBaseTest, VerifyAes128WithDecryption) { out.SegmentData() + out.SegmentSize()); ASSERT_TRUE(decryptor.Crypt(buffer, &decrypted)); - ASSERT_EQ(decrypted, exp_segment_buffer); + // TODO(Fordyce): with null packet stuffing this is no longer valid + // ASSERT_EQ(decrypted, exp_segment_buffer); } } @@ -610,6 +611,7 @@ TEST_F(LivePackagerBaseTest, CheckContinutityCounter) { ASSERT_FALSE(init_segment_buffer.empty()); media::ByteQueue ts_byte_queue; + int continuity_counter_tracker = 0; for (unsigned int i = 0; i < kNumSegments; i++) { std::string segment_num = absl::StrFormat("input/%04d.m4s", i); @@ -669,11 +671,15 @@ TEST_F(LivePackagerBaseTest, CheckContinutityCounter) { // check the PAT (PID = 0x0) or PMT (PID = 0x20) continuity counter is // in sync with the segment number. EXPECT_EQ(ts_packet->continuity_counter(), live_config.segment_number); + } else if (ts_packet->pid() == 0x80) { + // check PES TS Packets CC correctly increments. + int expected_continuity_counter = (continuity_counter_tracker++) % 16; + EXPECT_EQ(ts_packet->continuity_counter(), expected_continuity_counter); } // Go to the next packet. ts_byte_queue.Pop(media::mp2t::TsPacket::kPacketSize); } - + continuity_counter_tracker = 0; ts_byte_queue.Reset(); } } diff --git a/packager/media/formats/mp2t/continuity_counter.cc b/packager/media/formats/mp2t/continuity_counter.cc index ef769f05304..9e1edca9f0b 100644 --- a/packager/media/formats/mp2t/continuity_counter.cc +++ b/packager/media/formats/mp2t/continuity_counter.cc @@ -17,7 +17,9 @@ ContinuityCounter::ContinuityCounter(unsigned int segment_number) ContinuityCounter::~ContinuityCounter() = default; unsigned int ContinuityCounter::GetNext() { - unsigned int ret = ((counter_++) & 0x0F); + unsigned int ret = counter_; + ++counter_; + counter_ %= 16; return ret; } diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index a4b83a2a539..08b2c0b3de8 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -222,12 +222,13 @@ bool TsStuffingWriter::AddPesPacket(std::unique_ptr pes_packet, do { const int pid = ProgramMapTableWriter::kElementaryPid; BufferWriter null_ts_packet_buffer; - null_ts_packet_buffer.AppendInt( - static_cast(TsSection::kPidNullPacket)); + // TODO(Fordyce): do the stuffing packets need a payload? + // null_ts_packet_buffer.AppendInt(static_cast(TsSection::kPidNullPacket)); WritePayloadToBufferWriter(null_ts_packet_buffer.Buffer(), null_ts_packet_buffer.Size(), !kPayloadUnitStartIndicator, pid, !kHasPcr, 0, &elementary_stream_continuity_counter_, buffer); + } while ((elementary_stream_continuity_counter_.GetCurrent() & 0x0F) != 0); // No need to keep pes_packet around so not passing it anywhere. diff --git a/packager/media/formats/mp2t/ts_writer.h b/packager/media/formats/mp2t/ts_writer.h index 975f8e2a006..f1f037e34fd 100644 --- a/packager/media/formats/mp2t/ts_writer.h +++ b/packager/media/formats/mp2t/ts_writer.h @@ -56,6 +56,10 @@ class TsWriter { virtual bool AddPesPacket(std::unique_ptr pes_packet, BufferWriter* buffer); + // [[nodiscard]] ContinuityCounter es_continuity_counter() const { + // return elementary_stream_continuity_counter_; + // } + protected: ContinuityCounter elementary_stream_continuity_counter_; From 3d1ce098c814e4a492888f3ef5be35e897cbe28f Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Tue, 23 Jan 2024 15:18:52 -0700 Subject: [PATCH 05/18] feat: ensure live packager tests run in actions matrix --- .github/workflows/build.yaml | 8 ++++++++ packager/CMakeLists.txt | 1 + 2 files changed, 9 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f069a97479b..9a32680c358 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -87,6 +87,7 @@ jobs: os: ${{ fromJSON(needs.build_matrix_config.outputs.OS) }} build_type: ["Debug", "Release"] lib_type: ["static", "shared"] + live_packager_tests: ["enabled", "disables"] name: ${{ matrix.os_name }} ${{ matrix.target_arch }} ${{ matrix.build_type }} ${{ matrix.lib_type }} runs-on: ${{ matrix.os }} @@ -127,6 +128,12 @@ jobs: else BUILD_SHARED_LIBS="OFF" fi + + if [[ "${{ matrix.live_packager_tests }}" == "enabled" ]]; then + BUILD_LIVE_TEST="ON" + else + BUILD_LIVE_TEST="OFF" + fi # If set, override the default generator for the platform. # Not every entry in the build matrix config defines this. @@ -143,6 +150,7 @@ jobs: cmake \ -DCMAKE_BUILD_TYPE="${{ matrix.build_type }}" \ -DBUILD_SHARED_LIBS="$BUILD_SHARED_LIBS" \ + -DBUILD_LIVE_TEST="$BUILD_LIVE_TEST" \ -S . \ -B build/ diff --git a/packager/CMakeLists.txt b/packager/CMakeLists.txt index 6a1c31f835c..d83d93d40fd 100644 --- a/packager/CMakeLists.txt +++ b/packager/CMakeLists.txt @@ -230,6 +230,7 @@ if(BUILD_LIVE_TEST) gmock gtest gtest_main) + add_test(NAME live_packager_test COMMAND live_packager_test) endif() list(APPEND packager_test_py_sources From 90e0bd2464fc5f8b2cd432a1a4ccf7d0182623cf Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Tue, 23 Jan 2024 15:19:25 -0700 Subject: [PATCH 06/18] feat: ensure live packager tests run in actions matrix --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9a32680c358..645cdf5e655 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -87,7 +87,7 @@ jobs: os: ${{ fromJSON(needs.build_matrix_config.outputs.OS) }} build_type: ["Debug", "Release"] lib_type: ["static", "shared"] - live_packager_tests: ["enabled", "disables"] + live_packager_tests: ["enabled", "disabled"] name: ${{ matrix.os_name }} ${{ matrix.target_arch }} ${{ matrix.build_type }} ${{ matrix.lib_type }} runs-on: ${{ matrix.os }} From 8099c40b7f9f64335c0fcdfea77fb81c551796be Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Tue, 23 Jan 2024 16:17:18 -0700 Subject: [PATCH 07/18] fix: actions during testing --- .github/workflows/build.yaml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 645cdf5e655..cf4867a5032 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -87,7 +87,7 @@ jobs: os: ${{ fromJSON(needs.build_matrix_config.outputs.OS) }} build_type: ["Debug", "Release"] lib_type: ["static", "shared"] - live_packager_tests: ["enabled", "disabled"] +# live_packager_tests: ["enabled", "disabled"] name: ${{ matrix.os_name }} ${{ matrix.target_arch }} ${{ matrix.build_type }} ${{ matrix.lib_type }} runs-on: ${{ matrix.os }} @@ -125,15 +125,17 @@ jobs: if [[ "${{ matrix.lib_type }}" == "shared" ]]; then BUILD_SHARED_LIBS="ON" + BUILD_LIVE_TEST="ON" else BUILD_SHARED_LIBS="OFF" + BUILD_LIVE_TEST="OFF" fi - if [[ "${{ matrix.live_packager_tests }}" == "enabled" ]]; then - BUILD_LIVE_TEST="ON" - else - BUILD_LIVE_TEST="OFF" - fi +# if [[ "${{ matrix.live_packager_tests }}" == "enabled" ]]; then +# BUILD_LIVE_TEST="ON" +# else +# BUILD_LIVE_TEST="OFF" +# fi # If set, override the default generator for the platform. # Not every entry in the build matrix config defines this. From 1f13a9f8d3a492975ace213c394f2190012841f3 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Tue, 23 Jan 2024 16:19:58 -0700 Subject: [PATCH 08/18] fix: actions during testing --- .github/workflows/build.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cf4867a5032..4230c2e50f2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -87,7 +87,6 @@ jobs: os: ${{ fromJSON(needs.build_matrix_config.outputs.OS) }} build_type: ["Debug", "Release"] lib_type: ["static", "shared"] -# live_packager_tests: ["enabled", "disabled"] name: ${{ matrix.os_name }} ${{ matrix.target_arch }} ${{ matrix.build_type }} ${{ matrix.lib_type }} runs-on: ${{ matrix.os }} @@ -130,12 +129,6 @@ jobs: BUILD_SHARED_LIBS="OFF" BUILD_LIVE_TEST="OFF" fi - -# if [[ "${{ matrix.live_packager_tests }}" == "enabled" ]]; then -# BUILD_LIVE_TEST="ON" -# else -# BUILD_LIVE_TEST="OFF" -# fi # If set, override the default generator for the platform. # Not every entry in the build matrix config defines this. From 119b4dee90acb9a29865727263286547c46a7c05 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Tue, 23 Jan 2024 16:35:50 -0700 Subject: [PATCH 09/18] fix: unit test build warnings --- packager/live_packager_test.cc | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/packager/live_packager_test.cc b/packager/live_packager_test.cc index ef551c9dd17..52e66a78f41 100644 --- a/packager/live_packager_test.cc +++ b/packager/live_packager_test.cc @@ -22,12 +22,8 @@ #include #include #include -#include #include #include -#include -#include -#include #include #include #include @@ -343,8 +339,8 @@ void CheckSegment(const LiveConfig& config, const FullSegmentBuffer& buffer) { TEST(GeneratePSSHData, GeneratesPSSHBoxesAndMSPRObject) { PSSHGeneratorInput in{ .protection_scheme = PSSHGeneratorInput::MP4ProtectionSchemeFourCC::CENC, - .key_id = unhex("00000000621f2afe7ab2c868d5fd2e2e"), .key = unhex("1af987fa084ff3c0f4ad35a6bdab98e2"), + .key_id = unhex("00000000621f2afe7ab2c868d5fd2e2e"), .key_ids = {unhex("00000000621f2afe7ab2c868d5fd2e2e"), unhex("00000000621f2afe7ab2c868d5fd2e2f")}}; @@ -393,8 +389,8 @@ TEST(GeneratePSSHData, GeneratesPSSHBoxesAndMSPRObject) { TEST(GeneratePSSHData, FailsOnInvalidInput) { const PSSHGeneratorInput valid_input{ .protection_scheme = PSSHGeneratorInput::MP4ProtectionSchemeFourCC::CENC, - .key_id = unhex("00000000621f2afe7ab2c868d5fd2e2e"), .key = unhex("1af987fa084ff3c0f4ad35a6bdab98e2"), + .key_id = unhex("00000000621f2afe7ab2c868d5fd2e2e"), .key_ids = {unhex("00000000621f2afe7ab2c868d5fd2e2e"), unhex("00000000621f2afe7ab2c868d5fd2e2f")}}; @@ -433,7 +429,6 @@ TEST(GeneratePSSHData, FailsOnInvalidInput) { class LivePackagerBaseTest : public ::testing::Test { public: void SetUp() override { - mp2t_parser_ = std::make_unique(); key_.assign(kKey, kKey + std::size(kKey)); iv_.assign(kIv, kIv + std::size(kIv)); key_id_.assign(kKeyId, kKeyId + std::size(kKeyId)); @@ -463,8 +458,6 @@ class LivePackagerBaseTest : public ::testing::Test { std::vector key_; std::vector iv_; std::vector key_id_; - - std::unique_ptr mp2t_parser_; }; TEST_F(LivePackagerBaseTest, InitSegmentOnly) { @@ -737,7 +730,7 @@ class LivePackagerEncryptionTest } protected: - std::vector ReadExpectedData() { + static std::vector ReadExpectedData() { // TODO: make this more generic to handle mp2t as well std::vector buf = ReadTestDataFile("expected/fmp4/init.mp4"); for (unsigned int i = 0; i < GetParam().num_segments; i++) { @@ -749,7 +742,7 @@ class LivePackagerEncryptionTest return buf; } - std::unique_ptr MakeKeySource() { + static std::unique_ptr MakeKeySource() { RawKeyParams raw_key; RawKeyParams::KeyInfo& key_info = raw_key.key_map[""]; key_info.key = {std::begin(kKey), std::end(kKey)}; From 7080db858ba9ee772565bada6d2a1cd48686c382 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Tue, 23 Jan 2024 17:40:17 -0700 Subject: [PATCH 10/18] fix: PR feedback --- include/packager/packager.h | 3 ++ packager/app/muxer_factory.cc | 5 ++- packager/app/muxer_factory.h | 3 ++ packager/live_packager.cc | 1 + packager/media/base/muxer_options.h | 3 ++ .../media/formats/mp2t/continuity_counter.cc | 7 ++-- .../media/formats/mp2t/continuity_counter.h | 9 ++--- .../formats/mp2t/program_map_table_writer.cc | 13 +------ .../formats/mp2t/program_map_table_writer.h | 11 ++---- packager/media/formats/mp2t/ts_segmenter.cc | 21 ++++++++++- packager/media/formats/mp2t/ts_writer.cc | 37 +------------------ packager/media/formats/mp2t/ts_writer.h | 28 ++++---------- 12 files changed, 54 insertions(+), 87 deletions(-) diff --git a/include/packager/packager.h b/include/packager/packager.h index 1b94ab3d669..d90618662c9 100644 --- a/include/packager/packager.h +++ b/include/packager/packager.h @@ -75,6 +75,9 @@ struct PackagingParams { /// Only use to package init segment separately. bool init_segment_only = false; + + /// Specify weather or not to enable null packet stuffing for TS segments. + bool enable_null_ts_packet_stuffing = false; }; /// Defines a single input/output stream. diff --git a/packager/app/muxer_factory.cc b/packager/app/muxer_factory.cc index 653c1ed64de..e57b434b813 100644 --- a/packager/app/muxer_factory.cc +++ b/packager/app/muxer_factory.cc @@ -24,7 +24,9 @@ MuxerFactory::MuxerFactory(const PackagingParams& packaging_params) temp_dir_(packaging_params.temp_dir), transport_stream_timestamp_offset_ms_( packaging_params.transport_stream_timestamp_offset_ms), - init_segment_only_(packaging_params.init_segment_only) {} + init_segment_only_(packaging_params.init_segment_only), + enable_null_ts_packet_stuffing_( + packaging_params.enable_null_ts_packet_stuffing) {} std::shared_ptr MuxerFactory::CreateMuxer( MediaContainerName output_format, @@ -37,6 +39,7 @@ std::shared_ptr MuxerFactory::CreateMuxer( options.output_file_name = stream.output; options.segment_template = stream.segment_template; options.bandwidth = stream.bandwidth; + options.enable_null_ts_packet_stuffing = enable_null_ts_packet_stuffing_; std::shared_ptr muxer; diff --git a/packager/app/muxer_factory.h b/packager/app/muxer_factory.h index 13318e4b2aa..e4b6fa96df7 100644 --- a/packager/app/muxer_factory.h +++ b/packager/app/muxer_factory.h @@ -54,6 +54,9 @@ class MuxerFactory { // enable init segment packaging separately bool init_segment_only_; + + // enable null TS packet stuffing + bool enable_null_ts_packet_stuffing_; }; } // namespace media diff --git a/packager/live_packager.cc b/packager/live_packager.cc index 893da8d63c3..ef75ccb4716 100644 --- a/packager/live_packager.cc +++ b/packager/live_packager.cc @@ -330,6 +330,7 @@ Status LivePackager::Package(const Segment& init_segment, packaging_params.mp4_output_params.include_pssh_in_stream = false; packaging_params.transport_stream_timestamp_offset_ms = config_.m2ts_offset_ms; + packaging_params.enable_null_ts_packet_stuffing = true; EncryptionParams& encryption_params = packaging_params.encryption_params; // As a side effect of InitializeEncryption, encryption_params will be diff --git a/packager/media/base/muxer_options.h b/packager/media/base/muxer_options.h index ba207f934df..1e3cb6b5e31 100644 --- a/packager/media/base/muxer_options.h +++ b/packager/media/base/muxer_options.h @@ -44,6 +44,9 @@ struct MuxerOptions { /// User-specified bit rate for the media stream. If zero, the muxer will /// attempt to estimate. uint32_t bandwidth = 0; + + /// Specify weather or not to enable null packet stuffing for TS segments. + bool enable_null_ts_packet_stuffing = false; }; } // namespace media diff --git a/packager/media/formats/mp2t/continuity_counter.cc b/packager/media/formats/mp2t/continuity_counter.cc index 9e1edca9f0b..51f074177df 100644 --- a/packager/media/formats/mp2t/continuity_counter.cc +++ b/packager/media/formats/mp2t/continuity_counter.cc @@ -10,20 +10,19 @@ namespace shaka { namespace media { namespace mp2t { -ContinuityCounter::ContinuityCounter() : ContinuityCounter(0) {} -ContinuityCounter::ContinuityCounter(unsigned int segment_number) +ContinuityCounter::ContinuityCounter(int segment_number) : counter_(segment_number & 0xF) {} ContinuityCounter::~ContinuityCounter() = default; -unsigned int ContinuityCounter::GetNext() { +int ContinuityCounter::GetNext() { unsigned int ret = counter_; ++counter_; counter_ %= 16; return ret; } -unsigned int ContinuityCounter::GetCurrent() const { +int ContinuityCounter::GetCurrent() const { return counter_; } diff --git a/packager/media/formats/mp2t/continuity_counter.h b/packager/media/formats/mp2t/continuity_counter.h index 05dbb516687..76744bbf641 100644 --- a/packager/media/formats/mp2t/continuity_counter.h +++ b/packager/media/formats/mp2t/continuity_counter.h @@ -15,20 +15,19 @@ namespace mp2t { class ContinuityCounter { public: - ContinuityCounter(unsigned int segment_number); - ContinuityCounter(); + ContinuityCounter(int segment_number = 0); ~ContinuityCounter(); /// As specified by the spec, this starts from 0 and is incremented by 1 until /// it wraps back to 0 when it reaches 16. /// @return counter value. - unsigned int GetNext(); + int GetNext(); /// @return the current value of the continuity counter. - [[nodiscard]] unsigned int GetCurrent() const; + [[nodiscard]] int GetCurrent() const; private: - unsigned int counter_; + int counter_; DISALLOW_COPY_AND_ASSIGN(ContinuityCounter); }; diff --git a/packager/media/formats/mp2t/program_map_table_writer.cc b/packager/media/formats/mp2t/program_map_table_writer.cc index b76f0e9e97c..790d1d735bc 100644 --- a/packager/media/formats/mp2t/program_map_table_writer.cc +++ b/packager/media/formats/mp2t/program_map_table_writer.cc @@ -193,15 +193,12 @@ void WritePmtWithParameters(uint8_t stream_type, } // namespace -ProgramMapTableWriter::ProgramMapTableWriter(Codec codec) - : ProgramMapTableWriter(codec, 0) {} - // use segment number as continuity counter as PMT types have single packets, // therefore, using the segment number as the CC will be continuous across // segments ProgramMapTableWriter::ProgramMapTableWriter(Codec codec, unsigned int segment_number) - : codec_(codec), continuity_counter_(segment_number) {} + : codec_(codec), continuity_counter_(static_cast(segment_number)) {} bool ProgramMapTableWriter::EncryptedSegmentPmt(BufferWriter* writer) { if (encrypted_pmt_.Size() == 0) { @@ -273,9 +270,6 @@ bool ProgramMapTableWriter::ClearSegmentPmt(BufferWriter* writer) { return true; } -VideoProgramMapTableWriter::VideoProgramMapTableWriter(Codec codec) - : VideoProgramMapTableWriter(codec, 0) {} - VideoProgramMapTableWriter::VideoProgramMapTableWriter( Codec codec, unsigned int segment_number) @@ -296,11 +290,6 @@ bool VideoProgramMapTableWriter::WriteDescriptors( return true; } -AudioProgramMapTableWriter::AudioProgramMapTableWriter( - Codec codec, - const std::vector& audio_specific_config) - : AudioProgramMapTableWriter(codec, audio_specific_config, 0) {} - AudioProgramMapTableWriter::AudioProgramMapTableWriter( Codec codec, const std::vector& audio_specific_config, diff --git a/packager/media/formats/mp2t/program_map_table_writer.h b/packager/media/formats/mp2t/program_map_table_writer.h index 2a6e54cb6a1..f98409fd4ef 100644 --- a/packager/media/formats/mp2t/program_map_table_writer.h +++ b/packager/media/formats/mp2t/program_map_table_writer.h @@ -25,8 +25,7 @@ namespace mp2t { /// Puts PMT into TS packets and writes them to buffer. class ProgramMapTableWriter { public: - ProgramMapTableWriter(Codec codec); - ProgramMapTableWriter(Codec codec, unsigned int segment_number); + explicit ProgramMapTableWriter(Codec codec, unsigned int segment_number = 0); virtual ~ProgramMapTableWriter() = default; /// Writes TS packets with PMT for encrypted segments. @@ -64,8 +63,8 @@ class ProgramMapTableWriter { /// ProgramMapTableWriter for video codecs. class VideoProgramMapTableWriter : public ProgramMapTableWriter { public: - explicit VideoProgramMapTableWriter(Codec codec); - VideoProgramMapTableWriter(Codec codec, unsigned int segment_number); + explicit VideoProgramMapTableWriter(Codec codec, + unsigned int segment_number = 0); ~VideoProgramMapTableWriter() override = default; private: @@ -79,11 +78,9 @@ class VideoProgramMapTableWriter : public ProgramMapTableWriter { /// ProgramMapTableWriter for video codecs. class AudioProgramMapTableWriter : public ProgramMapTableWriter { public: - AudioProgramMapTableWriter(Codec codec, - const std::vector& audio_specific_config); AudioProgramMapTableWriter(Codec codec, const std::vector& audio_specific_config, - unsigned int segment_number); + unsigned int segment_number = 0); ~AudioProgramMapTableWriter() override = default; private: diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index c38bbfae3da..721cb314f58 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace shaka { @@ -101,8 +102,7 @@ Status TsSegmenter::AddSample(const MediaSample& sample) { DCHECK(IsVideoCodec(codec_)); pmt_writer.reset(new VideoProgramMapTableWriter(codec_, segment_number)); } - ts_writer_.reset( - new TsStuffingWriter(std::move(pmt_writer), segment_number)); + ts_writer_.reset(new TsWriter(std::move(pmt_writer), segment_number)); } if (sample.is_encrypted()) @@ -175,6 +175,23 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) { if (!status.ok()) return status; + if (muxer_options_.enable_null_ts_packet_stuffing) { + ContinuityCounter* es_continuity_counter = + ts_writer_->es_continuity_counter(); + if (es_continuity_counter) { + do { + const int pid = ProgramMapTableWriter::kElementaryPid; + BufferWriter null_ts_packet_buffer; + // TODO(Fordyce): do the stuffing packets need a payload? + // null_ts_packet_buffer.AppendInt(static_cast(TsSection::kPidNullPacket)); + WritePayloadToBufferWriter( + null_ts_packet_buffer.Buffer(), null_ts_packet_buffer.Size(), false, + pid, false, 0, es_continuity_counter, &segment_buffer_); + + } while ((es_continuity_counter->GetCurrent() & 0x0F) != 0); + } + } + // This method may be called from Finalize() so segment_started_ could // be false. if (!segment_started_) diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index 08b2c0b3de8..94711e097d0 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -162,18 +162,12 @@ bool WritePesToBuffer(const PesPacket& pes, } // namespace -TsWriter::TsWriter(std::unique_ptr pmt_writer) - : TsWriter(std::move(pmt_writer), 0) {} - TsWriter::TsWriter(std::unique_ptr pmt_writer, unsigned int segment_number) - : pat_continuity_counter_(segment_number), + : pat_continuity_counter_(static_cast(segment_number)), pmt_writer_(std::move(pmt_writer)) {} -TsStuffingWriter::TsStuffingWriter( - std::unique_ptr pmt_writer, - unsigned int segment_number) - : TsWriter(std::move(pmt_writer), segment_number) {} +TsWriter::~TsWriter() = default; bool TsWriter::NewSegment(BufferWriter* buffer) { BufferWriter psi; @@ -208,33 +202,6 @@ bool TsWriter::AddPesPacket(std::unique_ptr pes_packet, return true; } -bool TsStuffingWriter::AddPesPacket(std::unique_ptr pes_packet, - BufferWriter* buffer) { - if (!WritePesToBuffer(*pes_packet, &elementary_stream_continuity_counter_, - buffer)) { - LOG(ERROR) << "Failed to write pes to buffer."; - return false; - } - - // We must end all ES packets at 0xf so that the next segment can start at - // 0x0. This can be done by stuffing null packets at the end of the segment - // for each elementary stream - do { - const int pid = ProgramMapTableWriter::kElementaryPid; - BufferWriter null_ts_packet_buffer; - // TODO(Fordyce): do the stuffing packets need a payload? - // null_ts_packet_buffer.AppendInt(static_cast(TsSection::kPidNullPacket)); - WritePayloadToBufferWriter(null_ts_packet_buffer.Buffer(), - null_ts_packet_buffer.Size(), - !kPayloadUnitStartIndicator, pid, !kHasPcr, 0, - &elementary_stream_continuity_counter_, buffer); - - } while ((elementary_stream_continuity_counter_.GetCurrent() & 0x0F) != 0); - - // No need to keep pes_packet around so not passing it anywhere. - return true; -} - } // namespace mp2t } // namespace media } // namespace shaka diff --git a/packager/media/formats/mp2t/ts_writer.h b/packager/media/formats/mp2t/ts_writer.h index f1f037e34fd..d48596ee982 100644 --- a/packager/media/formats/mp2t/ts_writer.h +++ b/packager/media/formats/mp2t/ts_writer.h @@ -30,15 +30,13 @@ class ProgramMapTableWriter; /// the data to file. This also creates PSI from StreamInfo. class TsWriter { public: - TsWriter(std::unique_ptr pmt_writer); - /// Create TsWriter with segment_number /// @param pmt_writer the writes PMT into ts packets //// @param segment_number is used to set the continuity counter for PAT /// packets. - TsWriter(std::unique_ptr pmt_writer, - unsigned int segment_number); - virtual ~TsWriter() = default; + explicit TsWriter(std::unique_ptr pmt_writer, + unsigned int segment_number = 0); + virtual ~TsWriter(); /// This will fail if the current segment is not finalized. /// @param buffer to write segment data. @@ -56,12 +54,9 @@ class TsWriter { virtual bool AddPesPacket(std::unique_ptr pes_packet, BufferWriter* buffer); - // [[nodiscard]] ContinuityCounter es_continuity_counter() const { - // return elementary_stream_continuity_counter_; - // } - - protected: - ContinuityCounter elementary_stream_continuity_counter_; + ContinuityCounter* es_continuity_counter() { + return &elementary_stream_continuity_counter_; + } private: TsWriter(const TsWriter&) = delete; @@ -71,20 +66,11 @@ class TsWriter { bool encrypted_ = false; ContinuityCounter pat_continuity_counter_; + ContinuityCounter elementary_stream_continuity_counter_; std::unique_ptr pmt_writer_; }; -/// TsWriter to handle stuffing null TS Packets -class TsStuffingWriter : public TsWriter { - public: - TsStuffingWriter(std::unique_ptr pmt_writer, - unsigned int segment_number); - - bool AddPesPacket(std::unique_ptr pes_packet, - BufferWriter* buffer) override; -}; - } // namespace mp2t } // namespace media } // namespace shaka From ba4ccc596b9d16a9928e65ccf923890aa128b4f8 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Tue, 23 Jan 2024 17:41:50 -0700 Subject: [PATCH 11/18] fix: PR feedback --- packager/media/formats/mp2t/continuity_counter.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/media/formats/mp2t/continuity_counter.cc b/packager/media/formats/mp2t/continuity_counter.cc index 51f074177df..0fb16557aae 100644 --- a/packager/media/formats/mp2t/continuity_counter.cc +++ b/packager/media/formats/mp2t/continuity_counter.cc @@ -16,7 +16,7 @@ ContinuityCounter::ContinuityCounter(int segment_number) ContinuityCounter::~ContinuityCounter() = default; int ContinuityCounter::GetNext() { - unsigned int ret = counter_; + int ret = counter_; ++counter_; counter_ %= 16; return ret; From 394610cd14153c488b8c1103a4b299835a85a825 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Tue, 23 Jan 2024 18:30:46 -0700 Subject: [PATCH 12/18] fix: cmake for live packager test --- packager/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/CMakeLists.txt b/packager/CMakeLists.txt index d83d93d40fd..9004c750353 100644 --- a/packager/CMakeLists.txt +++ b/packager/CMakeLists.txt @@ -216,7 +216,7 @@ target_link_libraries(packager_test gtest_main) # Disabled by default, otherwise build live packager unit tests if BUILD_LIVE_TEST is on. -if(BUILD_LIVE_TEST) +if(BUILD_LIVE_TEST AND NOT MSVC) add_executable(live_packager_test live_packager_test.cc ) From ea2ed6e1bb5ecb59b2a553efb1c03685e6a2c397 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Wed, 24 Jan 2024 09:25:05 -0700 Subject: [PATCH 13/18] fix: include comments --- packager/media/formats/mp2t/ts_segmenter.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index 721cb314f58..9eec4135140 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -175,6 +175,10 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) { if (!status.ok()) return status; + // Since the ending CC of the previous segment could be anything, + // we must end all ES packets at 0xf so that the next segment can start + // at 0x0. This can be done by stuffing null packets at the end of the segment + // for each elementary stream if (muxer_options_.enable_null_ts_packet_stuffing) { ContinuityCounter* es_continuity_counter = ts_writer_->es_continuity_counter(); From cc49999298e4ed6482d12267ec998ba0732f9339 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Wed, 24 Jan 2024 16:44:39 -0700 Subject: [PATCH 14/18] fix: PR feedback --- packager/live_packager_test.cc | 16 ++++---------- .../media/formats/mp2t/continuity_counter.cc | 4 ++-- .../media/formats/mp2t/continuity_counter.h | 4 ++-- .../formats/mp2t/program_map_table_writer.cc | 2 +- packager/media/formats/mp2t/ts_segmenter.cc | 21 ++++++++----------- packager/media/formats/mp2t/ts_writer.cc | 2 +- packager/media/formats/mp2t/ts_writer.h | 4 ++-- 7 files changed, 21 insertions(+), 32 deletions(-) diff --git a/packager/live_packager_test.cc b/packager/live_packager_test.cc index 52e66a78f41..4b0f765b851 100644 --- a/packager/live_packager_test.cc +++ b/packager/live_packager_test.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -639,25 +640,16 @@ TEST_F(LivePackagerBaseTest, CheckContinutityCounter) { // Synchronization. int skipped_bytes = media::mp2t::TsPacket::Sync(ts_buffer, ts_buffer_size); - if (skipped_bytes > 0) { - LOG(WARNING) << "Packet not aligned on a TS syncword:" - << " skipped_bytes=" << skipped_bytes; - ts_byte_queue.Pop(skipped_bytes); - continue; - } + ASSERT_EQ(skipped_bytes, 0); // Parse the TS header, skipping 1 byte if the header is invalid. std::unique_ptr ts_packet( media::mp2t::TsPacket::Parse(ts_buffer, ts_buffer_size)); - if (!ts_packet) { - LOG(WARNING) << "Error: invalid TS packet"; - ts_byte_queue.Pop(1); - continue; - } + ASSERT_NE(nullptr, ts_packet); if (ts_packet->payload_unit_start_indicator() && (ts_packet->pid() == media::mp2t::TsSection::kPidPat || - ts_packet->pid() == 0x20)) { + ts_packet->pid() == media::mp2t::ProgramMapTableWriter::kPmtPid)) { LOG(INFO) << "Processing PID=" << ts_packet->pid() << " start_unit=" << ts_packet->payload_unit_start_indicator() << " continuity_counter=" << ts_packet->continuity_counter(); diff --git a/packager/media/formats/mp2t/continuity_counter.cc b/packager/media/formats/mp2t/continuity_counter.cc index 0fb16557aae..743f307db58 100644 --- a/packager/media/formats/mp2t/continuity_counter.cc +++ b/packager/media/formats/mp2t/continuity_counter.cc @@ -10,8 +10,8 @@ namespace shaka { namespace media { namespace mp2t { -ContinuityCounter::ContinuityCounter(int segment_number) - : counter_(segment_number & 0xF) {} +ContinuityCounter::ContinuityCounter(unsigned int segment_number) + : counter_(static_cast(segment_number) & 0xF) {} ContinuityCounter::~ContinuityCounter() = default; diff --git a/packager/media/formats/mp2t/continuity_counter.h b/packager/media/formats/mp2t/continuity_counter.h index 76744bbf641..74a609f189d 100644 --- a/packager/media/formats/mp2t/continuity_counter.h +++ b/packager/media/formats/mp2t/continuity_counter.h @@ -15,7 +15,7 @@ namespace mp2t { class ContinuityCounter { public: - ContinuityCounter(int segment_number = 0); + ContinuityCounter(unsigned int segment_number = 0); ~ContinuityCounter(); /// As specified by the spec, this starts from 0 and is incremented by 1 until @@ -27,7 +27,7 @@ class ContinuityCounter { [[nodiscard]] int GetCurrent() const; private: - int counter_; + int counter_ = 0; DISALLOW_COPY_AND_ASSIGN(ContinuityCounter); }; diff --git a/packager/media/formats/mp2t/program_map_table_writer.cc b/packager/media/formats/mp2t/program_map_table_writer.cc index 790d1d735bc..e243c91c129 100644 --- a/packager/media/formats/mp2t/program_map_table_writer.cc +++ b/packager/media/formats/mp2t/program_map_table_writer.cc @@ -198,7 +198,7 @@ void WritePmtWithParameters(uint8_t stream_type, // segments ProgramMapTableWriter::ProgramMapTableWriter(Codec codec, unsigned int segment_number) - : codec_(codec), continuity_counter_(static_cast(segment_number)) {} + : codec_(codec), continuity_counter_(segment_number) {} bool ProgramMapTableWriter::EncryptedSegmentPmt(BufferWriter* writer) { if (encrypted_pmt_.Size() == 0) { diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index 9eec4135140..8bf7475dc65 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -180,19 +180,16 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) { // at 0x0. This can be done by stuffing null packets at the end of the segment // for each elementary stream if (muxer_options_.enable_null_ts_packet_stuffing) { - ContinuityCounter* es_continuity_counter = + ContinuityCounter& es_continuity_counter = ts_writer_->es_continuity_counter(); - if (es_continuity_counter) { - do { - const int pid = ProgramMapTableWriter::kElementaryPid; - BufferWriter null_ts_packet_buffer; - // TODO(Fordyce): do the stuffing packets need a payload? - // null_ts_packet_buffer.AppendInt(static_cast(TsSection::kPidNullPacket)); - WritePayloadToBufferWriter( - null_ts_packet_buffer.Buffer(), null_ts_packet_buffer.Size(), false, - pid, false, 0, es_continuity_counter, &segment_buffer_); - - } while ((es_continuity_counter->GetCurrent() & 0x0F) != 0); + while (es_continuity_counter.GetCurrent() != 0) { + const int pid = ProgramMapTableWriter::kElementaryPid; + BufferWriter null_ts_packet_buffer; + // TODO(Fordyce): do the stuffing packets need a payload? + // null_ts_packet_buffer.AppendInt(static_cast(TsSection::kPidNullPacket)); + WritePayloadToBufferWriter( + null_ts_packet_buffer.Buffer(), null_ts_packet_buffer.Size(), false, + pid, false, 0, &es_continuity_counter, &segment_buffer_); } } diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index 94711e097d0..bd724566fd5 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -164,7 +164,7 @@ bool WritePesToBuffer(const PesPacket& pes, TsWriter::TsWriter(std::unique_ptr pmt_writer, unsigned int segment_number) - : pat_continuity_counter_(static_cast(segment_number)), + : pat_continuity_counter_(segment_number), pmt_writer_(std::move(pmt_writer)) {} TsWriter::~TsWriter() = default; diff --git a/packager/media/formats/mp2t/ts_writer.h b/packager/media/formats/mp2t/ts_writer.h index d48596ee982..29cfe9f7ee0 100644 --- a/packager/media/formats/mp2t/ts_writer.h +++ b/packager/media/formats/mp2t/ts_writer.h @@ -54,8 +54,8 @@ class TsWriter { virtual bool AddPesPacket(std::unique_ptr pes_packet, BufferWriter* buffer); - ContinuityCounter* es_continuity_counter() { - return &elementary_stream_continuity_counter_; + ContinuityCounter& es_continuity_counter() { + return elementary_stream_continuity_counter_; } private: From 857a285ce897b239a094d016c01e4cb3f1e29fd7 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Wed, 24 Jan 2024 16:49:55 -0700 Subject: [PATCH 15/18] fix: revert formatter --- packager/media/formats/mp2t/ts_segmenter.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index 8bf7475dc65..a2525bca3a8 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -198,8 +198,8 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) { if (!segment_started_) return Status::OK; std::string segment_path = - GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_, - segment_number_++, muxer_options_.bandwidth); + GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_, + segment_number_++, muxer_options_.bandwidth); const int64_t file_size = segment_buffer_.Size(); std::unique_ptr segment_file; @@ -215,14 +215,14 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) { return Status( error::FILE_FAILURE, "Cannot close file " + segment_path + - ", possibly file permission issue or running out of disk space."); + ", possibly file permission issue or running out of disk space."); } if (listener_) { - listener_->OnNewSegment( - segment_path, - start_timestamp * timescale_scale_ + transport_stream_timestamp_offset_, - duration * timescale_scale_, file_size); + listener_->OnNewSegment(segment_path, + start_timestamp * timescale_scale_ + + transport_stream_timestamp_offset_, + duration * timescale_scale_, file_size); } segment_started_ = false; From 0120de1951b21fbc1e2b462caf1e024c7c4228b0 Mon Sep 17 00:00:00 2001 From: "lee.fordyce" Date: Thu, 25 Jan 2024 12:19:57 -0700 Subject: [PATCH 16/18] fix: regenerate expected ts with packet stuffing --- packager/live_packager_test.cc | 7 ++++--- .../expected/stuffing_ts/0001.ts | Bin 0 -> 93624 bytes .../expected/stuffing_ts/0002.ts | Bin 0 -> 3781432 bytes .../expected/stuffing_ts/0003.ts | Bin 0 -> 1363000 bytes .../expected/stuffing_ts/0004.ts | Bin 0 -> 914808 bytes .../expected/stuffing_ts/0005.ts | Bin 0 -> 1197560 bytes .../expected/stuffing_ts/0006.ts | Bin 0 -> 854648 bytes .../expected/stuffing_ts/0007.ts | Bin 0 -> 1206584 bytes .../expected/stuffing_ts/0008.ts | Bin 0 -> 478648 bytes .../expected/stuffing_ts/0009.ts | Bin 0 -> 674168 bytes .../expected/stuffing_ts/0010.ts | Bin 0 -> 598968 bytes 11 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0001.ts create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0002.ts create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0003.ts create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0004.ts create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0005.ts create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0006.ts create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0007.ts create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0008.ts create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0009.ts create mode 100644 packager/media/test/data/live_packager_tests/expected/stuffing_ts/0010.ts diff --git a/packager/live_packager_test.cc b/packager/live_packager_test.cc index 4b0f765b851..bada3b13900 100644 --- a/packager/live_packager_test.cc +++ b/packager/live_packager_test.cc @@ -550,12 +550,14 @@ TEST_F(LivePackagerBaseTest, VerifyAes128WithDecryption) { live_config.format = LiveConfig::OutputFormat::TS; live_config.track_type = LiveConfig::TrackType::VIDEO; live_config.protection_scheme = LiveConfig::EncryptionScheme::AES_128; + live_config.segment_number = i; SetupLivePackagerConfig(live_config); ASSERT_EQ(Status::OK, live_packager_->Package(init_seg, media_seg, out)); ASSERT_GT(out.SegmentSize(), 0); - std::string exp_segment_num = absl::StrFormat("expected/ts/%04d.ts", i + 1); + std::string exp_segment_num = + absl::StrFormat("expected/stuffing_ts/%04d.ts", i + 1); std::vector exp_segment_buffer = ReadTestDataFile(exp_segment_num); ASSERT_FALSE(exp_segment_buffer.empty()); @@ -564,8 +566,7 @@ TEST_F(LivePackagerBaseTest, VerifyAes128WithDecryption) { out.SegmentData() + out.SegmentSize()); ASSERT_TRUE(decryptor.Crypt(buffer, &decrypted)); - // TODO(Fordyce): with null packet stuffing this is no longer valid - // ASSERT_EQ(decrypted, exp_segment_buffer); + ASSERT_EQ(decrypted, exp_segment_buffer); } } diff --git a/packager/media/test/data/live_packager_tests/expected/stuffing_ts/0001.ts b/packager/media/test/data/live_packager_tests/expected/stuffing_ts/0001.ts new file mode 100644 index 0000000000000000000000000000000000000000..e88fe40b54351a327a199d005ca2df6309e95ab1 GIT binary patch literal 93624 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