diff --git a/docs/source/options/chunking_options.rst b/docs/source/options/chunking_options.rst index 560a2a7cc8..4bb909e043 100644 --- a/docs/source/options/chunking_options.rst +++ b/docs/source/options/chunking_options.rst @@ -21,3 +21,7 @@ Chunking options Force fragments to begin with stream access points. This flag implies *segment_sap_aligned*. Default enabled. + +--start_segment_number + + Indicates the startNumber in DASH SegmentTemplate and HLS segment name. diff --git a/include/packager/chunking_params.h b/include/packager/chunking_params.h index e12baf0b9e..ddc31675c9 100644 --- a/include/packager/chunking_params.h +++ b/include/packager/chunking_params.h @@ -31,6 +31,9 @@ struct ChunkingParams { /// and mdat atom. Each chunk is uploaded immediately upon creation, /// decoupling latency from segment duration. bool low_latency_dash_mode = false; + + /// Indicates the startNumber in DASH SegmentTemplate and HLS segment name. + int64_t start_segment_number = 1; }; } // namespace shaka diff --git a/packager/app/muxer_flags.cc b/packager/app/muxer_flags.cc index 2a68b1ee91..be6c3520b4 100644 --- a/packager/app/muxer_flags.cc +++ b/packager/app/muxer_flags.cc @@ -73,3 +73,9 @@ ABSL_FLAG( "If the first sample comes after default_text_zero_bias_ms then the start " "of the stream will not be padded as we cannot assume the start time of " "the stream."); + +ABSL_FLAG(int64_t, + start_segment_number, + 1, + "Indicates the startNumber in DASH SegmentTemplate and HLS " + "segment name."); \ No newline at end of file diff --git a/packager/app/muxer_flags.h b/packager/app/muxer_flags.h index a27aff8638..527ca77939 100644 --- a/packager/app/muxer_flags.h +++ b/packager/app/muxer_flags.h @@ -22,5 +22,6 @@ ABSL_DECLARE_FLAG(std::string, temp_dir); ABSL_DECLARE_FLAG(bool, mp4_include_pssh_in_stream); ABSL_DECLARE_FLAG(int32_t, transport_stream_timestamp_offset_ms); ABSL_DECLARE_FLAG(int32_t, default_text_zero_bias_ms); +ABSL_DECLARE_FLAG(int64_t, start_segment_number); #endif // APP_MUXER_FLAGS_H_ diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 34e27256a7..8e542aeef6 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -360,6 +360,8 @@ std::optional GetPackagingParams() { absl::GetFlag(FLAGS_segment_sap_aligned); chunking_params.subsegment_sap_aligned = absl::GetFlag(FLAGS_fragment_sap_aligned); + chunking_params.start_segment_number = + absl::GetFlag(FLAGS_start_segment_number); int num_key_providers = 0; EncryptionParams& encryption_params = packaging_params.encryption_params; diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 6df148c753..0bdde8e1a3 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -485,8 +485,8 @@ class PackagerAppTest(unittest.TestCase): use_fake_clock=True, allow_codec_switching=False, dash_force_segment_list=False, - force_cl_index=None): - + force_cl_index=None, + start_segment_number=None): flags = ['--single_threaded'] if not strip_parameter_set_nalus: @@ -575,6 +575,9 @@ class PackagerAppTest(unittest.TestCase): elif force_cl_index is False: flags += ['--noforce_cl_index'] + if start_segment_number is not None: + flags += ['--start_segment_number', str(start_segment_number)] + if ad_cues: flags += ['--ad_cues', ad_cues] @@ -823,6 +826,13 @@ class PackagerFunctionalTest(PackagerAppTest): output_hls=True)) self._CheckTestResults('forced-subtitle') + def testDashStartNumber(self): + audio_video_streams = self._GetStreams(['audio', 'video'], segmented=True) + streams = audio_video_streams + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + start_segment_number=0)) + self._CheckTestResults('dash-start-number') + def testAudioVideoWithLanguageOverride(self): self.assertPackageSuccess( self._GetStreams(['audio', 'video'], language='por', hls=True), diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-0.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-0.m4s new file mode 100644 index 0000000000..6e56dd8555 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-0.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-1.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-1.m4s new file mode 100644 index 0000000000..5bf76aa0d1 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-1.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-2.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-2.m4s new file mode 100644 index 0000000000..d6bcbb5de8 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-2.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-init.mp4 b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-init.mp4 new file mode 100644 index 0000000000..3176a92d7f Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-init.mp4 differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-0.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-video-0.m4s new file mode 100644 index 0000000000..2c90c46883 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-0.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-1.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-video-1.m4s new file mode 100644 index 0000000000..c6b356a8e9 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-1.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-2.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-video-2.m4s new file mode 100644 index 0000000000..4edeba974e Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-2.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-init.mp4 b/packager/app/test/testdata/dash-start-number/bear-640x360-video-init.mp4 new file mode 100644 index 0000000000..874b16341f Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-init.mp4 differ diff --git a/packager/app/test/testdata/dash-start-number/output.mpd b/packager/app/test/testdata/dash-start-number/output.mpd new file mode 100644 index 0000000000..81965d0568 --- /dev/null +++ b/packager/app/test/testdata/dash-start-number/output.mpd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 3dd79e3265..ca5ac9f09c 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -737,9 +737,9 @@ void MediaPlaylist::RemoveOldSegment(int64_t start_time) { if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) return; - segments_to_be_removed_.push_back( - media::GetSegmentName(media_info_.segment_template(), start_time, - media_sequence_number_, media_info_.bandwidth())); + segments_to_be_removed_.push_back(media::GetSegmentName( + media_info_.segment_template(), start_time, media_sequence_number_ + 1, + media_info_.bandwidth())); while (segments_to_be_removed_.size() > hls_params_.preserved_segments_outside_live_window) { VLOG(2) << "Deleting " << segments_to_be_removed_.front(); diff --git a/packager/media/base/media_handler.h b/packager/media/base/media_handler.h index e2975ad52e..9608145a2b 100644 --- a/packager/media/base/media_handler.h +++ b/packager/media/base/media_handler.h @@ -59,6 +59,7 @@ struct SegmentInfo { bool is_encrypted = false; int64_t start_timestamp = -1; int64_t duration = 0; + int64_t segment_number = 1; // This is only available if key rotation is enabled. Note that we may have // a |key_rotation_encryption_config| even if the segment is not encrypted, // which is the case for clear lead. diff --git a/packager/media/base/media_handler_test_base.cc b/packager/media/base/media_handler_test_base.cc index 0a5b28e91c..8a5a7d1e03 100644 --- a/packager/media/base/media_handler_test_base.cc +++ b/packager/media/base/media_handler_test_base.cc @@ -251,11 +251,13 @@ std::shared_ptr MediaHandlerTestBase::GetMediaSample( std::unique_ptr MediaHandlerTestBase::GetSegmentInfo( int64_t start_timestamp, int64_t duration, - bool is_subsegment) const { + bool is_subsegment, + int64_t segment_number) const { std::unique_ptr info(new SegmentInfo); info->start_timestamp = start_timestamp; info->duration = duration; info->is_subsegment = is_subsegment; + info->segment_number = segment_number; return info; } diff --git a/packager/media/base/media_handler_test_base.h b/packager/media/base/media_handler_test_base.h index 980948a5e4..6b5cf3c274 100644 --- a/packager/media/base/media_handler_test_base.h +++ b/packager/media/base/media_handler_test_base.h @@ -325,7 +325,8 @@ class MediaHandlerTestBase : public ::testing::Test { std::unique_ptr GetSegmentInfo(int64_t start_timestamp, int64_t duration, - bool is_subsegment) const; + bool is_subsegment, + int64_t segment_number) const; std::unique_ptr GetTextStreamInfo(int32_t timescale) const; diff --git a/packager/media/base/muxer.h b/packager/media/base/muxer.h index 6a622172ae..9e99762383 100644 --- a/packager/media/base/muxer.h +++ b/packager/media/base/muxer.h @@ -117,7 +117,7 @@ class Muxer : public MediaHandler { // In VOD single segment case with Ad Cues, |output_file_name| is allowed to // be a template. In this case, there will be NumAdCues + 1 files generated. std::string output_file_template_; - size_t output_file_index_ = 0; + size_t output_file_index_ = 1; }; } // namespace media diff --git a/packager/media/base/muxer_util.cc b/packager/media/base/muxer_util.cc index bd6028da6f..a928d868f9 100644 --- a/packager/media/base/muxer_util.cc +++ b/packager/media/base/muxer_util.cc @@ -110,7 +110,7 @@ Status ValidateSegmentTemplate(const std::string& segment_template) { std::string GetSegmentName(const std::string& segment_template, int64_t segment_start_time, - uint32_t segment_index, + uint32_t segment_number, uint32_t bandwidth) { DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template)); @@ -154,7 +154,7 @@ std::string GetSegmentName(const std::string& segment_template, absl::UntypedFormatSpec format(format_tag); if (identifier == "Number") { // SegmentNumber starts from 1. - format_args.emplace_back(static_cast(segment_index + 1)); + format_args.emplace_back(static_cast(segment_number)); } else if (identifier == "Time") { format_args.emplace_back(static_cast(segment_start_time)); } else if (identifier == "Bandwidth") { diff --git a/packager/media/base/muxer_util.h b/packager/media/base/muxer_util.h index 02473ecb51..7cf797c022 100644 --- a/packager/media/base/muxer_util.h +++ b/packager/media/base/muxer_util.h @@ -29,12 +29,12 @@ Status ValidateSegmentTemplate(const std::string& segment_template); /// @param segment_template is the segment template pattern, which should /// comply with ISO/IEC 23009-1:2012 5.3.9.4.4. /// @param segment_start_time specifies the segment start time. -/// @param segment_index specifies the segment index. +/// @param segment_number specifies the segment number. /// @param bandwidth represents the bit rate, in bits/sec, of the stream. /// @return The segment name with identifier substituted. std::string GetSegmentName(const std::string& segment_template, int64_t segment_start_time, - uint32_t segment_index, + uint32_t segment_number, uint32_t bandwidth); } // namespace media diff --git a/packager/media/base/muxer_util_unittest.cc b/packager/media/base/muxer_util_unittest.cc index 0f410ff885..58e58122b4 100644 --- a/packager/media/base/muxer_util_unittest.cc +++ b/packager/media/base/muxer_util_unittest.cc @@ -62,95 +62,57 @@ TEST(MuxerUtilTest, ValidateSegmentTemplateWithFormatTag) { TEST(MuxerUtilTest, GetSegmentName) { const int64_t kSegmentStartTime = 180180; - const uint32_t kSegmentIndex = 11; + const uint32_t kSegmentNumber = 12; const uint32_t kBandwidth = 1234; - EXPECT_EQ("12", GetSegmentName("$Number$", - kSegmentStartTime, - kSegmentIndex, + EXPECT_EQ("12", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber, kBandwidth)); - EXPECT_EQ("012", - GetSegmentName("$Number%03d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - EXPECT_EQ( - "12$foo$00012", - GetSegmentName( - "$Number%01d$$$foo$$$Number%05d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("012", GetSegmentName("$Number%03d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + EXPECT_EQ("12$foo$00012", + GetSegmentName("$Number%01d$$$foo$$$Number%05d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); - EXPECT_EQ("180180", - GetSegmentName("$Time$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("180180", GetSegmentName("$Time$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); EXPECT_EQ("foo$_$18018000180180.m4s", GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + kSegmentStartTime, kSegmentNumber, kBandwidth)); // Combo values. - EXPECT_EQ("12-1234", - GetSegmentName("$Number$-$Bandwidth$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("12-1234", GetSegmentName("$Number$-$Bandwidth$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); EXPECT_EQ("012-001234", - GetSegmentName("$Number%03d$-$Bandwidth%06d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + GetSegmentName("$Number%03d$-$Bandwidth%06d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); // Format specifier edge cases. - EXPECT_EQ("12", - GetSegmentName("$Number%00d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - EXPECT_EQ("00012", - GetSegmentName("$Number%005d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("12", GetSegmentName("$Number%00d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + EXPECT_EQ("00012", GetSegmentName("$Number%005d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) { const int64_t kSegmentStartTime = 0; - const uint32_t kSegmentIndex = 0; + const uint32_t kSegmentNumber = 1; const uint32_t kBandwidth = 0; - EXPECT_EQ("1", GetSegmentName("$Number$", - kSegmentStartTime, - kSegmentIndex, + EXPECT_EQ("1", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber, kBandwidth)); - EXPECT_EQ("001", - GetSegmentName("$Number%03d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("001", GetSegmentName("$Number%03d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); - EXPECT_EQ("0", GetSegmentName("$Time$", - kSegmentStartTime, - kSegmentIndex, + EXPECT_EQ("0", GetSegmentName("$Time$", kSegmentStartTime, kSegmentNumber, kBandwidth)); - EXPECT_EQ("00000000.m4s", - GetSegmentName("$Time%08d$.m4s", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("00000000.m4s", GetSegmentName("$Time%08d$.m4s", kSegmentStartTime, + kSegmentNumber, kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameLargeTime) { const int64_t kSegmentStartTime = 1601599839840ULL; - const uint32_t kSegmentIndex = 8888888; + const uint32_t kSegmentNumber = 8888889; const uint32_t kBandwidth = 444444; - EXPECT_EQ("1601599839840", - GetSegmentName("$Time$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("1601599839840", GetSegmentName("$Time$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); } } // namespace media diff --git a/packager/media/base/text_muxer.cc b/packager/media/base/text_muxer.cc index 70c967423c..e6e07dcab1 100644 --- a/packager/media/base/text_muxer.cc +++ b/packager/media/base/text_muxer.cc @@ -50,9 +50,13 @@ Status TextMuxer::Finalize() { // Insert a dummy value so the HLS generator will generate a segment list. ranges.subsegment_ranges.emplace_back(); + // The segment number does not matter for single segment output. + const uint32_t kArbitrarySegmentNumber = 0; + muxer_listener()->OnNewSegment( options().output_file_name, 0, - duration_seconds * streams()[0]->time_scale(), size); + duration_seconds * streams()[0]->time_scale(), size, + kArbitrarySegmentNumber); } muxer_listener()->OnMediaEnd(ranges, duration_seconds); @@ -82,17 +86,20 @@ Status TextMuxer::FinalizeSegment(size_t stream_id, const std::string& segment_template = options().segment_template; DCHECK(!segment_template.empty()); - const uint32_t index = segment_index_++; + const int64_t start = segment_info.start_timestamp; const int64_t duration = segment_info.duration; + const uint32_t segment_number = segment_info.segment_number; + const uint32_t bandwidth = options().bandwidth; const std::string filename = - GetSegmentName(segment_template, start, index, bandwidth); + GetSegmentName(segment_template, start, segment_number, bandwidth); uint64_t size; RETURN_IF_ERROR(WriteToFile(filename, &size)); - muxer_listener()->OnNewSegment(filename, start, duration, size); + muxer_listener()->OnNewSegment(filename, start, duration, size, + segment_number); return Status::OK; } diff --git a/packager/media/base/text_muxer.h b/packager/media/base/text_muxer.h index 0b4c9be58d..68ad1bd18f 100644 --- a/packager/media/base/text_muxer.h +++ b/packager/media/base/text_muxer.h @@ -38,7 +38,6 @@ class TextMuxer : public Muxer { int64_t total_duration_ms_ = 0; int64_t last_cue_ms_ = 0; - uint32_t segment_index_ = 0; }; } // namespace media diff --git a/packager/media/chunking/chunking_handler.cc b/packager/media/chunking/chunking_handler.cc index bcabf96127..388683a867 100644 --- a/packager/media/chunking/chunking_handler.cc +++ b/packager/media/chunking/chunking_handler.cc @@ -34,6 +34,7 @@ bool IsNewSegmentIndex(int64_t new_index, int64_t current_index) { ChunkingHandler::ChunkingHandler(const ChunkingParams& chunking_params) : chunking_params_(chunking_params) { CHECK_NE(chunking_params.segment_duration_in_seconds, 0u); + segment_number_ = chunking_params.start_segment_number; } Status ChunkingHandler::InitializeInternal() { @@ -163,17 +164,20 @@ Status ChunkingHandler::OnMediaSample( return DispatchMediaSample(kStreamIndex, std::move(sample)); } -Status ChunkingHandler::EndSegmentIfStarted() const { +Status ChunkingHandler::EndSegmentIfStarted() { if (!segment_start_time_) return Status::OK; auto segment_info = std::make_shared(); segment_info->start_timestamp = segment_start_time_.value(); segment_info->duration = max_segment_time_ - segment_start_time_.value(); + segment_info->segment_number = segment_number_++; + if (chunking_params_.low_latency_dash_mode) { segment_info->is_chunk = true; segment_info->is_final_chunk_in_seg = true; } + return DispatchSegmentInfo(kStreamIndex, std::move(segment_info)); } diff --git a/packager/media/chunking/chunking_handler.h b/packager/media/chunking/chunking_handler.h index 717ccec3e1..8706e2bf12 100644 --- a/packager/media/chunking/chunking_handler.h +++ b/packager/media/chunking/chunking_handler.h @@ -60,7 +60,7 @@ class ChunkingHandler : public MediaHandler { Status OnCueEvent(std::shared_ptr event); Status OnMediaSample(std::shared_ptr sample); - Status EndSegmentIfStarted() const; + Status EndSegmentIfStarted(); Status EndSubsegmentIfStarted() const; bool IsSubsegmentEnabled() { @@ -74,8 +74,13 @@ class ChunkingHandler : public MediaHandler { int64_t segment_duration_ = 0; int64_t subsegment_duration_ = 0; + // Segment number that keeps monotically increasing. + // Set to start_segment_number in constructor. + int64_t segment_number_ = 1; + // Current segment index, useful to determine where to do chunking. int64_t current_segment_index_ = -1; + // Current subsegment index, useful to determine where to do chunking. int64_t current_subsegment_index_ = -1; diff --git a/packager/media/chunking/text_chunker.cc b/packager/media/chunking/text_chunker.cc index 6e4d5760a3..2f25ac7a28 100644 --- a/packager/media/chunking/text_chunker.cc +++ b/packager/media/chunking/text_chunker.cc @@ -16,8 +16,10 @@ namespace { const size_t kStreamIndex = 0; } // namespace -TextChunker::TextChunker(double segment_duration_in_seconds) - : segment_duration_in_seconds_(segment_duration_in_seconds){}; +TextChunker::TextChunker(double segment_duration_in_seconds, + int64_t start_segment_number) + : segment_duration_in_seconds_(segment_duration_in_seconds), + segment_number_(start_segment_number){}; Status TextChunker::Process(std::unique_ptr data) { switch (data->stream_data_type) { @@ -60,14 +62,12 @@ Status TextChunker::OnCueEvent(std::shared_ptr event) { // Convert the event's time to be scaled to the time of each sample. const int64_t event_time = ScaleTime(event->time_in_seconds); - // Output all full segments before the segment that the cue event interupts. while (segment_start_ + segment_duration_ < event_time) { RETURN_IF_ERROR(DispatchSegment(segment_duration_)); } const int64_t shorten_duration = event_time - segment_start_; - RETURN_IF_ERROR(DispatchSegment(shorten_duration)); return DispatchCueEvent(kStreamIndex, std::move(event)); } @@ -109,6 +109,8 @@ Status TextChunker::DispatchSegment(int64_t duration) { std::shared_ptr info = std::make_shared(); info->start_timestamp = segment_start_; info->duration = duration; + info->segment_number = segment_number_++; + RETURN_IF_ERROR(DispatchSegmentInfo(kStreamIndex, std::move(info))); // Move onto the next segment. diff --git a/packager/media/chunking/text_chunker.h b/packager/media/chunking/text_chunker.h index e6f75c4ca6..b4e836d09d 100644 --- a/packager/media/chunking/text_chunker.h +++ b/packager/media/chunking/text_chunker.h @@ -20,7 +20,8 @@ namespace media { // is when a cue event is seen. class TextChunker : public MediaHandler { public: - explicit TextChunker(double segment_duration_in_seconds); + explicit TextChunker(double segment_duration_in_seconds, + int64_t start_segment_number); private: TextChunker(const TextChunker&) = delete; @@ -52,6 +53,10 @@ class TextChunker : public MediaHandler { int64_t segment_start_ = -1; // Set when the first sample comes in. int64_t segment_duration_ = -1; // Set in OnStreamInfo. + // Segment number that keeps monotically increasing. + // Set to start_segment_number in constructor. + int64_t segment_number_ = 1; + // All samples that make up the current segment. We must store the samples // until the segment ends because a cue event may end the segment sooner // than we expected. diff --git a/packager/media/chunking/text_chunker_unittest.cc b/packager/media/chunking/text_chunker_unittest.cc index 69372f9d0a..d1f4a0f0bf 100644 --- a/packager/media/chunking/text_chunker_unittest.cc +++ b/packager/media/chunking/text_chunker_unittest.cc @@ -30,6 +30,8 @@ const size_t kOutput = 0; const bool kEncrypted = true; const bool kSubSegment = true; +const int64_t kStartSegmentNumber = 1; + const char* kNoId = ""; const char* kNoPayload = ""; } // namespace @@ -38,7 +40,8 @@ class TextChunkerTest : public MediaHandlerTestBase { protected: Status Init(double segment_duration) { return SetUpAndInitializeGraph( - std::make_shared(segment_duration), kInputs, kOutputs); + std::make_shared(segment_duration, kStartSegmentNumber), + kInputs, kOutputs); } }; diff --git a/packager/media/crypto/encryption_handler_unittest.cc b/packager/media/crypto/encryption_handler_unittest.cc index a45790a72c..a1833def74 100644 --- a/packager/media/crypto/encryption_handler_unittest.cc +++ b/packager/media/crypto/encryption_handler_unittest.cc @@ -357,7 +357,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithNoKeyRotation) { kIsKeyFrame, kData, kDataSize)))); ASSERT_OK(Process(StreamData::FromSegmentInfo( kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration, - !kIsSubsegment)))); + !kIsSubsegment, i + 1)))); const bool is_encrypted = i == 2; const auto& output_stream_data = GetOutputStreamDataVector(); EXPECT_THAT(output_stream_data, @@ -436,7 +436,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) { kIsKeyFrame, kData, kDataSize)))); ASSERT_OK(Process(StreamData::FromSegmentInfo( kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration, - !kIsSubsegment)))); + !kIsSubsegment, i)))); const bool is_encrypted = i >= 2; const auto& output_stream_data = GetOutputStreamDataVector(); EXPECT_THAT(output_stream_data, diff --git a/packager/media/event/combined_muxer_listener.cc b/packager/media/event/combined_muxer_listener.cc index a113565519..76381b7c36 100644 --- a/packager/media/event/combined_muxer_listener.cc +++ b/packager/media/event/combined_muxer_listener.cc @@ -71,9 +71,11 @@ void CombinedMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void CombinedMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { for (auto& listener : muxer_listeners_) { - listener->OnNewSegment(file_name, start_time, duration, segment_file_size); + listener->OnNewSegment(file_name, start_time, duration, segment_file_size, + segment_number); } } diff --git a/packager/media/event/combined_muxer_listener.h b/packager/media/event/combined_muxer_listener.h index fdeda6123c..1f40e5c1e1 100644 --- a/packager/media/event/combined_muxer_listener.h +++ b/packager/media/event/combined_muxer_listener.h @@ -44,7 +44,8 @@ class CombinedMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnCompletedSegment(int64_t duration, uint64_t segment_file_size) override; void OnKeyFrame(int64_t timestamp, diff --git a/packager/media/event/event_info.h b/packager/media/event/event_info.h index ea1162a36e..0b8a4a9302 100644 --- a/packager/media/event/event_info.h +++ b/packager/media/event/event_info.h @@ -20,6 +20,7 @@ struct SegmentEventInfo { // The below two fields are only useful for Segment. int64_t duration; uint64_t segment_file_size; + int64_t segment_number; }; struct KeyFrameEvent { diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index c0f71267a3..3e61976651 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -244,11 +244,13 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { if (!media_info_->has_segment_template()) { EventInfo event_info; event_info.type = EventInfoType::kSegment; - event_info.segment_info = {start_time, duration, segment_file_size}; + event_info.segment_info = {start_time, duration, segment_file_size, + segment_number}; event_info_.push_back(event_info); } else { // For multisegment, it always starts from the beginning of the file. diff --git a/packager/media/event/hls_notify_muxer_listener.h b/packager/media/event/hls_notify_muxer_listener.h index 9d0f58c0b3..aa76fa8e84 100644 --- a/packager/media/event/hls_notify_muxer_listener.h +++ b/packager/media/event/hls_notify_muxer_listener.h @@ -72,7 +72,8 @@ class HlsNotifyMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size) override; diff --git a/packager/media/event/hls_notify_muxer_listener_unittest.cc b/packager/media/event/hls_notify_muxer_listener_unittest.cc index 4d9650a8e9..1bf9e2e4f1 100644 --- a/packager/media/event/hls_notify_muxer_listener_unittest.cc +++ b/packager/media/event/hls_notify_muxer_listener_unittest.cc @@ -79,6 +79,7 @@ const uint64_t kSegmentStartOffset = 10000; const int64_t kSegmentStartTime = 19283; const int64_t kSegmentDuration = 98028; const uint64_t kSegmentSize = 756739; +const int64_t kAnySegmentNumber = 10; const int64_t kCueStartTime = kSegmentStartTime; @@ -349,7 +350,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) { kSegmentDuration, _, kSegmentSize)); listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, kAnySegmentNumber); } // Verify that the notifier is called for every segment in OnMediaEnd if @@ -367,7 +368,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) { listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kCueStartTime)); EXPECT_CALL( @@ -397,7 +398,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { listener_.OnMediaStart(muxer_options1, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename1.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); listener_.OnCueEvent(kCueStartTime, "dummy cue data"); EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) @@ -414,7 +415,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { listener_.OnMediaStart(muxer_options2, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename2.mp4", kSegmentStartTime + kSegmentDuration, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, kAnySegmentNumber); EXPECT_CALL(mock_notifier_, NotifyNewSegment(_, StrEq("filename2.mp4"), kSegmentStartTime + kSegmentDuration, _, _, _)); @@ -440,7 +441,7 @@ TEST_F(HlsNotifyMuxerListenerTest, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); EXPECT_CALL( mock_notifier_, NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime, @@ -504,7 +505,7 @@ TEST_P(HlsNotifyMuxerListenerKeyFrameTest, NoSegmentTemplate) { listener_.OnKeyFrame(kKeyFrameTimestamp, kKeyFrameStartByteOffset, kKeyFrameSize); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); EXPECT_CALL(mock_notifier_, NotifyKeyFrame(_, kKeyFrameTimestamp, diff --git a/packager/media/event/mock_muxer_listener.h b/packager/media/event/mock_muxer_listener.h index 82713ae3f5..970bc3b118 100644 --- a/packager/media/event/mock_muxer_listener.h +++ b/packager/media/event/mock_muxer_listener.h @@ -56,11 +56,12 @@ class MockMuxerListener : public MuxerListener { void OnMediaEnd(const MediaRanges& range, float duration_seconds) override; - MOCK_METHOD4(OnNewSegment, + MOCK_METHOD5(OnNewSegment, void(const std::string& segment_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size)); + uint64_t segment_file_size, + int64_t segment_number)); MOCK_METHOD3(OnKeyFrame, void(int64_t timestamp, diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index 530c843421..79b58d8cbf 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -182,7 +182,8 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, mpd_notifier_->NotifyNewSegment( notification_id_.value(), event_info.segment_info.start_time, event_info.segment_info.duration, - event_info.segment_info.segment_file_size); + event_info.segment_info.segment_file_size, + event_info.segment_info.segment_number); break; case EventInfoType::kKeyFrame: // NO-OP for DASH. @@ -200,17 +201,20 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { UNUSED(file_name); if (mpd_notifier_->dash_profile() == DashProfile::kLive) { mpd_notifier_->NotifyNewSegment(notification_id_.value(), start_time, - duration, segment_file_size); + duration, segment_file_size, + segment_number); if (mpd_notifier_->mpd_type() == MpdType::kDynamic) mpd_notifier_->Flush(); } else { EventInfo event_info; event_info.type = EventInfoType::kSegment; - event_info.segment_info = {start_time, duration, segment_file_size}; + event_info.segment_info = {start_time, duration, segment_file_size, + segment_number}; event_info_.push_back(event_info); } } diff --git a/packager/media/event/mpd_notify_muxer_listener.h b/packager/media/event/mpd_notify_muxer_listener.h index 94375fc41b..98697a01e7 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -52,7 +52,8 @@ class MpdNotifyMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnCompletedSegment(int64_t duration, uint64_t segment_file_size) override; void OnKeyFrame(int64_t timestamp, diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 0cf6c3e10c..841a581849 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -397,27 +397,31 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); InSequence s; EXPECT_CALL(*notifier_, NotifyNewContainer( ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -433,29 +437,33 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegmentSegmentList) { const int64_t kStartTime1 = 0; const int64_t kDuration1 = 1000; const uint64_t kSegmentFileSize1 = 29812u; + const int64_t kSegmentNumber1 = 1; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); InSequence s; EXPECT_CALL(*notifier_, NotifyNewContainer( ExpectMediaInfoEq(kExpectedDefaultMediaInfoSubsegmentRange), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -485,15 +493,18 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; // Expectation for first file before OnMediaEnd. EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options1, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); @@ -502,8 +513,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { EXPECT_CALL(*notifier_, NotifyNewContainer(EqualsProto(expected_media_info1), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); @@ -512,13 +523,14 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { listener_->OnMediaStart(muxer_options2, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); // Expectation for second file OnMediaEnd. EXPECT_CALL(*notifier_, NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -544,17 +556,20 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) { const int64_t kStartTime1 = 0; const int64_t kDuration1 = 1000; const uint64_t kSegmentFileSize1 = 29812u; + const int64_t kSegmentNumber1 = 1; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; // Expectation for first file before OnMediaEnd. EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options1, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); @@ -563,8 +578,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) { EXPECT_CALL(*notifier_, NotifyNewContainer(EqualsProto(expected_media_info1), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); @@ -573,13 +588,14 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) { listener_->OnMediaStart(muxer_options2, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); // Expectation for second file OnMediaEnd. EXPECT_CALL(*notifier_, NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -613,6 +629,8 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { const uint64_t kDuration = 1000u; const uint64_t kSegmentSize1 = 29812u; const uint64_t kSegmentSize2 = 30128u; + const int64_t kSegmentNumber1 = 1; + const int64_t kSegmentNumber2 = 2; EXPECT_CALL(*notifier_, NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) @@ -622,11 +640,11 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { EXPECT_CALL(*notifier_, NotifyAvailabilityTimeOffset(_)) .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifySegmentDuration(_)).WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration, kSegmentSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration, + kSegmentSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration, kSegmentSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration, + kSegmentSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()).Times(2); listener_->OnMediaStart(muxer_options, *video_stream_info, @@ -635,9 +653,11 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { listener_->OnSampleDurationReady(kDuration); listener_->OnAvailabilityOffsetReady(); listener_->OnSegmentDurationReady(); - listener_->OnNewSegment("", kStartTime1, kDuration, kSegmentSize1); + listener_->OnNewSegment("", kStartTime1, kDuration, kSegmentSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration, kSegmentSize2); + listener_->OnNewSegment("", kStartTime2, kDuration, kSegmentSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()).Times(0); @@ -686,7 +706,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; + const std::vector default_key_id( kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1); @@ -695,14 +718,14 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { EXPECT_CALL(*notifier_, NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); // Flush should only be called once in OnMediaEnd. if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); @@ -713,9 +736,11 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()) @@ -760,7 +785,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; + const std::vector default_key_id( kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1); @@ -769,13 +797,13 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); // Flush should only be called once in OnMediaEnd. if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); @@ -789,8 +817,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { listener_->OnEncryptionInfoReady(kNonInitialEncryptionInfo, FOURCC_cbc1, std::vector(), iv, GetDefaultKeySystemInfo()); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()) diff --git a/packager/media/event/multi_codec_muxer_listener_unittest.cc b/packager/media/event/multi_codec_muxer_listener_unittest.cc index 6a75983d30..64d79d848f 100644 --- a/packager/media/event/multi_codec_muxer_listener_unittest.cc +++ b/packager/media/event/multi_codec_muxer_listener_unittest.cc @@ -27,6 +27,8 @@ const int64_t kSegmentStartTime = 19283; const int64_t kSegmentDuration = 98028; const uint64_t kSegmentSize = 756739; const int32_t kTimescale = 90000; +const int64_t kSegmentNumber = 10; + MuxerListener::ContainerType kContainer = MuxerListener::kContainerMpeg2ts; } // namespace @@ -79,10 +81,11 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartSingleCodec) { EXPECT_CALL(*listener_for_first_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, kSegmentNumber)); multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, + kSegmentNumber); } TEST_F(MultiCodecMuxerListenerTest, OnMediaStartTwoCodecs) { @@ -114,13 +117,14 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartTwoCodecs) { EXPECT_CALL(*listener_for_first_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, kSegmentNumber)); EXPECT_CALL(*listener_for_second_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, kSegmentNumber)); multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, + kSegmentNumber); } } // namespace media diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index b0a86e0fba..2eb704154c 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -133,10 +133,12 @@ class MuxerListener { /// @param duration is the duration of the segment, relative to the timescale /// specified by MediaInfo passed to OnMediaStart(). /// @param segment_file_size is the segment size in bytes. + /// @param segment_number is the segment number. virtual void OnNewSegment(const std::string& segment_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) = 0; + uint64_t segment_file_size, + int64_t segment_number) = 0; /// Called when a segment has been muxed and the entire file has been written. /// For Low Latency only. Note that it should be called after OnNewSegment. diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.cc b/packager/media/event/vod_media_info_dump_muxer_listener.cc index e0108f6831..148503c581 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener.cc @@ -95,7 +95,8 @@ void VodMediaInfoDumpMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void VodMediaInfoDumpMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { UNUSED(file_name); UNUSED(start_time); const double segment_duration_seconds = diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.h b/packager/media/event/vod_media_info_dump_muxer_listener.h index 4eacb4c671..42dd57da22 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.h +++ b/packager/media/event/vod_media_info_dump_muxer_listener.h @@ -49,7 +49,8 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size) override; diff --git a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc index f3926cd3a1..aa81cf1990 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc @@ -102,8 +102,10 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test { } void FireOnNewSegmentWithParams(const OnNewSegmentParameters& params) { + const int64_t kSegmentNumber = 1; listener_->OnNewSegment(params.file_name, params.start_time, - params.duration, params.segment_file_size); + params.duration, params.segment_file_size, + kSegmentNumber); } void FireOnMediaEndWithParams(const OnMediaEndParameters& params) { diff --git a/packager/media/formats/mp2t/ts_muxer.cc b/packager/media/formats/mp2t/ts_muxer.cc index c2a65f2653..636a1200f7 100644 --- a/packager/media/formats/mp2t/ts_muxer.cc +++ b/packager/media/formats/mp2t/ts_muxer.cc @@ -82,7 +82,7 @@ Status TsMuxer::FinalizeSegment(size_t stream_id, options().segment_template.empty() ? options().output_file_name : GetSegmentName(options().segment_template, segment_start_timestamp, - segment_number_++, options().bandwidth); + segment_info.segment_number, options().bandwidth); const int64_t file_size = segmenter_->segment_buffer()->Size(); @@ -95,7 +95,8 @@ Status TsMuxer::FinalizeSegment(size_t stream_id, segment_path, segment_info.start_timestamp * segmenter_->timescale() + segmenter_->transport_stream_timestamp_offset(), - segment_info.duration * segmenter_->timescale(), file_size); + segment_info.duration * segmenter_->timescale(), file_size, + segment_info.segment_number); } segmenter_->set_segment_started(false); diff --git a/packager/media/formats/mp2t/ts_muxer.h b/packager/media/formats/mp2t/ts_muxer.h index d184c52587..3a04efdbce 100644 --- a/packager/media/formats/mp2t/ts_muxer.h +++ b/packager/media/formats/mp2t/ts_muxer.h @@ -41,9 +41,6 @@ class TsMuxer : public Muxer { int64_t sample_durations_[2] = {0, 0}; size_t num_samples_ = 0; - // Used in multi-segment mode for segment template. - uint64_t segment_number_ = 0; - // Used in single segment mode. std::unique_ptr output_file_; diff --git a/packager/media/formats/mp2t/ts_segmenter.h b/packager/media/formats/mp2t/ts_segmenter.h index e551feea87..8a4eb7bcba 100644 --- a/packager/media/formats/mp2t/ts_segmenter.h +++ b/packager/media/formats/mp2t/ts_segmenter.h @@ -54,6 +54,7 @@ class TsSegmenter { /// stream's time scale. /// @param duration is the segment's duration in the input stream's time /// scale. + /// @param segment_number is the segment number. // TODO(kqyang): Remove the usage of segment start timestamp and duration in // xx_segmenter, which could cause confusions on which is the source of truth // as the segment start timestamp and duration could be tracked locally. diff --git a/packager/media/formats/mp2t/ts_segmenter_unittest.cc b/packager/media/formats/mp2t/ts_segmenter_unittest.cc index 8e3a45ba4a..7751e1a561 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -84,6 +84,7 @@ class MockTsWriter : public TsWriter { MOCK_METHOD1(NewSegment, bool(BufferWriter* buffer_writer)); MOCK_METHOD0(SignalEncrypted, void()); + MOCK_METHOD0(FinalizeSegment, bool()); // Similar to the hack above but takes a std::unique_ptr. MOCK_METHOD2(AddPesPacketMock, bool(PesPacket* pes_packet, @@ -232,7 +233,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) .InSequence(ready_pes_sequence) .WillOnce(Return(1u)); - + EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) .InSequence(ready_pes_sequence) .WillOnce(Return(0u)); diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index 78457c64fb..1aeb3b9bbb 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -188,8 +188,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/mp4/low_latency_segment_segmenter.cc b/packager/media/formats/mp4/low_latency_segment_segmenter.cc index cbb1449acc..22476461dc 100644 --- a/packager/media/formats/mp4/low_latency_segment_segmenter.cc +++ b/packager/media/formats/mp4/low_latency_segment_segmenter.cc @@ -71,13 +71,13 @@ Status LowLatencySegmentSegmenter::DoFinalize() { return Status::OK; } -Status LowLatencySegmentSegmenter::DoFinalizeSegment() { +Status LowLatencySegmentSegmenter::DoFinalizeSegment(int64_t segment_number) { return FinalizeSegment(); } -Status LowLatencySegmentSegmenter::DoFinalizeChunk() { +Status LowLatencySegmentSegmenter::DoFinalizeChunk(int64_t segment_number) { if (is_initial_chunk_in_seg_) { - return WriteInitialChunk(); + return WriteInitialChunk(segment_number); } return WriteChunk(); } @@ -98,7 +98,7 @@ Status LowLatencySegmentSegmenter::WriteInitSegment() { return buffer->WriteToFile(file.get()); } -Status LowLatencySegmentSegmenter::WriteInitialChunk() { +Status LowLatencySegmentSegmenter::WriteInitialChunk(int64_t segment_number) { DCHECK(sidx()); DCHECK(fragment_buffer()); DCHECK(styp_); @@ -161,9 +161,9 @@ Status LowLatencySegmentSegmenter::WriteInitialChunk() { } // Add the current segment in the manifest. // Following chunks will be appended to the open segment file. - muxer_listener()->OnNewSegment(file_name_, - sidx()->earliest_presentation_time, - segment_duration, segment_size_); + muxer_listener()->OnNewSegment( + file_name_, sidx()->earliest_presentation_time, segment_duration, + segment_size_, segment_number); is_initial_chunk_in_seg_ = false; } diff --git a/packager/media/formats/mp4/low_latency_segment_segmenter.h b/packager/media/formats/mp4/low_latency_segment_segmenter.h index 0db73df8b0..5f2e577dda 100644 --- a/packager/media/formats/mp4/low_latency_segment_segmenter.h +++ b/packager/media/formats/mp4/low_latency_segment_segmenter.h @@ -44,13 +44,13 @@ class LowLatencySegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; - Status DoFinalizeChunk() override; + Status DoFinalizeSegment(int64_t segment_number) override; + Status DoFinalizeChunk(int64_t segment_number) override; // Write segment to file. Status WriteInitSegment(); Status WriteChunk(); - Status WriteInitialChunk(); + Status WriteInitialChunk(int64_t segment_number); Status FinalizeSegment(); uint64_t GetSegmentDuration(); diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index a599283839..20907423b9 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -209,7 +209,8 @@ Status MP4Muxer::FinalizeSegment(size_t stream_id, DCHECK(segmenter_); VLOG(3) << "Finalizing " << (segment_info.is_subsegment ? "sub" : "") << "segment " << segment_info.start_timestamp << " duration " - << segment_info.duration; + << segment_info.duration << " segment number " + << segment_info.segment_number; return segmenter_->FinalizeSegment(stream_id, segment_info); } diff --git a/packager/media/formats/mp4/multi_segment_segmenter.cc b/packager/media/formats/mp4/multi_segment_segmenter.cc index 5381a308e7..bab13ed39e 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.cc +++ b/packager/media/formats/mp4/multi_segment_segmenter.cc @@ -31,8 +31,7 @@ MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options, std::unique_ptr ftyp, std::unique_ptr moov) : Segmenter(options, std::move(ftyp), std::move(moov)), - styp_(new SegmentType), - num_segments_(0) { + styp_(new SegmentType) { // Use the same brands for styp as ftyp. styp_->major_brand = Segmenter::ftyp()->major_brand; styp_->compatible_brands = Segmenter::ftyp()->compatible_brands; @@ -70,8 +69,8 @@ Status MultiSegmentSegmenter::DoFinalize() { return Status::OK; } -Status MultiSegmentSegmenter::DoFinalizeSegment() { - return WriteSegment(); +Status MultiSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) { + return WriteSegment(segment_number); } Status MultiSegmentSegmenter::WriteInitSegment() { @@ -90,7 +89,7 @@ Status MultiSegmentSegmenter::WriteInitSegment() { return buffer->WriteToFile(file.get()); } -Status MultiSegmentSegmenter::WriteSegment() { +Status MultiSegmentSegmenter::WriteSegment(int64_t segment_number) { DCHECK(sidx()); DCHECK(fragment_buffer()); DCHECK(styp_); @@ -115,7 +114,7 @@ Status MultiSegmentSegmenter::WriteSegment() { } else { file_name = GetSegmentName(options().segment_template, sidx()->earliest_presentation_time, - num_segments_++, options().bandwidth); + segment_number, options().bandwidth); file.reset(File::Open(file_name.c_str(), "w")); if (!file) { return Status(error::FILE_FAILURE, @@ -160,9 +159,9 @@ Status MultiSegmentSegmenter::WriteSegment() { UpdateProgress(segment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment(file_name, - sidx()->earliest_presentation_time, - segment_duration, segment_size); + muxer_listener()->OnNewSegment( + file_name, sidx()->earliest_presentation_time, segment_duration, + segment_size, segment_number); } return Status::OK; diff --git a/packager/media/formats/mp4/multi_segment_segmenter.h b/packager/media/formats/mp4/multi_segment_segmenter.h index ab19465d7d..3fd206dbbb 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.h +++ b/packager/media/formats/mp4/multi_segment_segmenter.h @@ -39,14 +39,13 @@ class MultiSegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; + Status DoFinalizeSegment(int64_t segment_number) override; // Write segment to file. Status WriteInitSegment(); - Status WriteSegment(); + Status WriteSegment(int64_t segment_number); std::unique_ptr styp_; - uint32_t num_segments_; DISALLOW_COPY_AND_ASSIGN(MultiSegmentSegmenter); }; diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 9313a0eb5e..a9b180a258 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -234,14 +234,15 @@ Status Segmenter::FinalizeSegment(size_t stream_id, if (segment_info.is_chunk) { // Finalize the completed chunk for the LL-DASH case. - status = DoFinalizeChunk(); + status = DoFinalizeChunk(segment_info.segment_number); if (!status.ok()) return status; } if (!segment_info.is_subsegment || segment_info.is_final_chunk_in_seg) { // Finalize the segment. - status = DoFinalizeSegment(); + status = DoFinalizeSegment(segment_info.segment_number); + // Reset segment information to initial state. sidx_->references.clear(); key_frame_infos_.clear(); diff --git a/packager/media/formats/mp4/segmenter.h b/packager/media/formats/mp4/segmenter.h index df48812122..51e659b35c 100644 --- a/packager/media/formats/mp4/segmenter.h +++ b/packager/media/formats/mp4/segmenter.h @@ -127,9 +127,8 @@ class Segmenter { private: virtual Status DoInitialize() = 0; virtual Status DoFinalize() = 0; - virtual Status DoFinalizeSegment() = 0; - - virtual Status DoFinalizeChunk() { return Status::OK; } + virtual Status DoFinalizeSegment(int64_t segment_number) = 0; + virtual Status DoFinalizeChunk(int64_t segment_number) { return Status::OK; } uint32_t GetReferenceStreamId(); diff --git a/packager/media/formats/mp4/single_segment_segmenter.cc b/packager/media/formats/mp4/single_segment_segmenter.cc index 13527ce446..afedf4ac7a 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.cc +++ b/packager/media/formats/mp4/single_segment_segmenter.cc @@ -166,7 +166,7 @@ Status SingleSegmentSegmenter::DoFinalize() { return Status::OK; } -Status SingleSegmentSegmenter::DoFinalizeSegment() { +Status SingleSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) { DCHECK(sidx()); DCHECK(fragment_buffer()); // sidx() contains pre-generated segment references with one reference per @@ -224,9 +224,9 @@ Status SingleSegmentSegmenter::DoFinalizeSegment() { UpdateProgress(vod_ref.subsegment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment(options().output_file_name, - vod_ref.earliest_presentation_time, - vod_ref.subsegment_duration, segment_size); + muxer_listener()->OnNewSegment( + options().output_file_name, vod_ref.earliest_presentation_time, + vod_ref.subsegment_duration, segment_size, segment_number); } return Status::OK; } diff --git a/packager/media/formats/mp4/single_segment_segmenter.h b/packager/media/formats/mp4/single_segment_segmenter.h index edfe618f8e..02fe763088 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.h +++ b/packager/media/formats/mp4/single_segment_segmenter.h @@ -44,7 +44,7 @@ class SingleSegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; + Status DoFinalizeSegment(int64_t segment_number) override; std::unique_ptr vod_sidx_; std::string temp_file_name_; diff --git a/packager/media/formats/packed_audio/packed_audio_writer.cc b/packager/media/formats/packed_audio/packed_audio_writer.cc index f73e09faa0..14a3d2febb 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer.cc @@ -81,7 +81,7 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id, options().segment_template.empty() ? options().output_file_name : GetSegmentName(options().segment_template, segment_timestamp, - segment_number_++, options().bandwidth); + segment_info.segment_number, options().bandwidth); // Save |segment_size| as it will be cleared after writing. const size_t segment_size = segmenter_->segment_buffer()->Size(); @@ -92,7 +92,8 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id, if (muxer_listener()) { muxer_listener()->OnNewSegment( segment_path, segment_timestamp + transport_stream_timestamp_offset_, - segment_info.duration * segmenter_->TimescaleScale(), segment_size); + segment_info.duration * segmenter_->TimescaleScale(), segment_size, + segment_info.segment_number); } return Status::OK; } diff --git a/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc b/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc index 910e8fa3a0..637b498382 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc @@ -44,6 +44,8 @@ const char kOutputFile[] = "memory://test.aac"; const char kSegmentTemplate[] = "memory://test_$Number$.aac"; const char kSegment1Name[] = "memory://test_1.aac"; const char kSegment2Name[] = "memory://test_2.aac"; +const int64_t kSegmentNumber1 = 1; +const int64_t kSegmentNumber2 = 2; class MockPackedAudioSegmenter : public PackedAudioSegmenter { public: @@ -122,9 +124,10 @@ TEST_P(PackedAudioWriterTest, SubsegmentIgnored) { const int64_t kDuration = 100; const bool kSubsegment = true; auto subsegment_stream_data = StreamData::FromSegmentInfo( - kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, kSubsegment)); + kStreamIndex, + GetSegmentInfo(kTimestamp, kDuration, kSubsegment, kSegmentNumber1)); - EXPECT_CALL(*mock_muxer_listener_ptr_, OnNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*mock_muxer_listener_ptr_, OnNewSegment(_, _, _, _, _)).Times(0); EXPECT_CALL(*mock_segmenter_ptr_, FinalizeSegment()).Times(0); ASSERT_OK(Input(kInput)->Dispatch(std::move(subsegment_stream_data))); } @@ -137,7 +140,8 @@ TEST_P(PackedAudioWriterTest, OneSegment) { const int64_t kDuration = 100; const bool kSubsegment = true; auto segment_stream_data = StreamData::FromSegmentInfo( - kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, !kSubsegment)); + kStreamIndex, + GetSegmentInfo(kTimestamp, kDuration, !kSubsegment, kSegmentNumber1)); const double kMockTimescaleScale = 10; const char kMockSegmentData[] = "hello segment 1"; @@ -147,7 +151,8 @@ TEST_P(PackedAudioWriterTest, OneSegment) { *mock_muxer_listener_ptr_, OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, kTimestamp * kMockTimescaleScale, - kDuration * kMockTimescaleScale, kSegmentDataSize)); + kDuration * kMockTimescaleScale, kSegmentDataSize, + kSegmentNumber1)); EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) .WillRepeatedly(Return(kMockTimescaleScale)); @@ -190,10 +195,11 @@ TEST_P(PackedAudioWriterTest, TwoSegments) { const int64_t kDuration = 100; const bool kSubsegment = true; auto segment1_stream_data = StreamData::FromSegmentInfo( - kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, !kSubsegment)); - auto segment2_stream_data = StreamData::FromSegmentInfo( kStreamIndex, - GetSegmentInfo(kTimestamp + kDuration, kDuration, !kSubsegment)); + GetSegmentInfo(kTimestamp, kDuration, !kSubsegment, kSegmentNumber1)); + auto segment2_stream_data = StreamData::FromSegmentInfo( + kStreamIndex, GetSegmentInfo(kTimestamp + kDuration, kDuration, + !kSubsegment, kSegmentNumber2)); const double kMockTimescaleScale = 10; const char kMockSegment1Data[] = "hello segment 1"; @@ -206,12 +212,13 @@ TEST_P(PackedAudioWriterTest, TwoSegments) { OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, kTimestamp * kMockTimescaleScale, kDuration * kMockTimescaleScale, - sizeof(kMockSegment1Data) - 1)); + sizeof(kMockSegment1Data) - 1, kSegmentNumber1)); EXPECT_CALL( *mock_muxer_listener_ptr_, OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment2Name, (kTimestamp + kDuration) * kMockTimescaleScale, - kDuration * kMockTimescaleScale, kSegment2DataSize)); + kDuration * kMockTimescaleScale, kSegment2DataSize, + kSegmentNumber2)); EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) .WillRepeatedly(Return(kMockTimescaleScale)); diff --git a/packager/media/formats/webm/encrypted_segmenter_unittest.cc b/packager/media/formats/webm/encrypted_segmenter_unittest.cc index 51ee4be820..7c270830cb 100644 --- a/packager/media/formats/webm/encrypted_segmenter_unittest.cc +++ b/packager/media/formats/webm/encrypted_segmenter_unittest.cc @@ -18,6 +18,9 @@ const int32_t kTimeScale = 1000000; const int64_t kDuration = 1000000; const bool kSubsegment = true; const uint8_t kPerSampleIvSize = 8u; +const int64_t kSegmentNumber1 = 1; +const int64_t kSegmentNumber2 = 2; + const uint8_t kKeyId[] = { 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, @@ -229,7 +232,8 @@ TEST_F(EncryptedSegmenterTest, BasicSupport) { // segment encrypted. for (int i = 0; i < 5; i++) { if (i == 3) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment, + kSegmentNumber1)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); @@ -243,8 +247,8 @@ TEST_F(EncryptedSegmenterTest, BasicSupport) { } ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK( - segmenter_->FinalizeSegment(3 * kDuration, 2 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(3 * kDuration, 2 * kDuration, + !kSubsegment, kSegmentNumber2)); ASSERT_OK(segmenter_->Finalize()); ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData); diff --git a/packager/media/formats/webm/multi_segment_segmenter.cc b/packager/media/formats/webm/multi_segment_segmenter.cc index d88a046f7a..7a2f6e7f49 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.cc +++ b/packager/media/formats/webm/multi_segment_segmenter.cc @@ -27,17 +27,18 @@ MultiSegmentSegmenter::~MultiSegmentSegmenter() {} Status MultiSegmentSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration_timestamp, - bool is_subsegment) { + bool is_subsegment, + int64_t segment_number) { CHECK(cluster()); RETURN_IF_ERROR(Segmenter::FinalizeSegment( - start_timestamp, duration_timestamp, is_subsegment)); + start_timestamp, duration_timestamp, is_subsegment, segment_number)); if (!cluster()->Finalize()) return Status(error::FILE_FAILURE, "Error finalizing segment."); if (!is_subsegment) { std::string segment_name = GetSegmentName(options().segment_template, start_timestamp, - num_segment_, options().bandwidth); + segment_number, options().bandwidth); // Close the file, which also does flushing, to make sure the file is // written before manifest is updated. @@ -54,7 +55,7 @@ Status MultiSegmentSegmenter::FinalizeSegment(int64_t start_timestamp, if (muxer_listener()) { const uint64_t size = cluster()->Size(); muxer_listener()->OnNewSegment(segment_name, start_timestamp, - duration_timestamp, size); + duration_timestamp, size, segment_number); } VLOG(1) << "WEBM file '" << segment_name << "' finalized."; } diff --git a/packager/media/formats/webm/multi_segment_segmenter.h b/packager/media/formats/webm/multi_segment_segmenter.h index 56a4c0fbfb..39db84c4d8 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.h +++ b/packager/media/formats/webm/multi_segment_segmenter.h @@ -32,7 +32,8 @@ class MultiSegmentSegmenter : public Segmenter { /// @{ Status FinalizeSegment(int64_t start_timestamp, int64_t duration_timestamp, - bool is_subsegment) override; + bool is_subsegment, + int64_t segment_number) override; bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; std::vector GetSegmentRanges() override; diff --git a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc index 5eb35ffda2..33a7c12b24 100644 --- a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc @@ -17,6 +17,8 @@ namespace { const int32_t kTimeScale = 1000000; const int64_t kDuration = 1000000; +const int64_t kSegmentNumber1 = 1; +const int64_t kSegmentNumber2 = 2; const bool kSubsegment = true; // clang-format off @@ -130,15 +132,16 @@ TEST_F(MultiSegmentSegmenterTest, BasicSupport) { CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment, + kSegmentNumber1)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportDataInit); - ASSERT_FILE_EQ(TemplateFileName(0).c_str(), kBasicSupportDataSegment); + ASSERT_FILE_EQ(TemplateFileName(1).c_str(), kBasicSupportDataSegment); // There is no second segment. - EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r")); + EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r")); } TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) { @@ -149,27 +152,28 @@ TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) { // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { if (i == 5) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment, + kSegmentNumber1)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK( - segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, + !kSubsegment, kSegmentNumber2)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. ClusterParser parser; - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); + ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1))); ASSERT_EQ(1u, parser.cluster_count()); EXPECT_EQ(5u, parser.GetFrameCountForCluster(0)); - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1))); + ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(2))); ASSERT_EQ(1u, parser.cluster_count()); EXPECT_EQ(3u, parser.GetFrameCountForCluster(0)); - EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r")); + EXPECT_FALSE(File::Open(TemplateFileName(3).c_str(), "r")); } TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) { @@ -180,23 +184,25 @@ TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) { // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { if (i == 5) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment, + kSegmentNumber1)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment, + kSegmentNumber1)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. ClusterParser parser; - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); + ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1))); ASSERT_EQ(2u, parser.cluster_count()); EXPECT_EQ(5u, parser.GetFrameCountForCluster(0)); EXPECT_EQ(3u, parser.GetFrameCountForCluster(1)); - EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r")); + EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r")); } } // namespace media diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index f05834c87d..09a69ae27f 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -198,7 +198,8 @@ Status Segmenter::AddSample(const MediaSample& source_sample) { Status Segmenter::FinalizeSegment(int64_t /*start_timestamp*/, int64_t /*duration_timestamp*/, - bool is_subsegment) { + bool is_subsegment, + int64_t segment_number) { if (is_subsegment) new_subsegment_ = true; else diff --git a/packager/media/formats/webm/segmenter.h b/packager/media/formats/webm/segmenter.h index 7a40cf73f5..d9196d98f4 100644 --- a/packager/media/formats/webm/segmenter.h +++ b/packager/media/formats/webm/segmenter.h @@ -58,7 +58,8 @@ class Segmenter { /// Finalize the (sub)segment. virtual Status FinalizeSegment(int64_t start_timestamp, int64_t duration_timestamp, - bool is_subsegment) = 0; + bool is_subsegment, + int64_t segment_number) = 0; /// @return true if there is an initialization range, while setting @a start /// and @a end; or false if initialization range does not apply. diff --git a/packager/media/formats/webm/single_segment_segmenter.cc b/packager/media/formats/webm/single_segment_segmenter.cc index adafcee867..b688c102ba 100644 --- a/packager/media/formats/webm/single_segment_segmenter.cc +++ b/packager/media/formats/webm/single_segment_segmenter.cc @@ -23,9 +23,10 @@ SingleSegmentSegmenter::~SingleSegmentSegmenter() {} Status SingleSegmentSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration_timestamp, - bool is_subsegment) { - Status status = Segmenter::FinalizeSegment(start_timestamp, - duration_timestamp, is_subsegment); + bool is_subsegment, + int64_t segment_number) { + Status status = Segmenter::FinalizeSegment( + start_timestamp, duration_timestamp, is_subsegment, segment_number); if (!status.ok()) return status; // No-op for subsegment in single segment mode. @@ -37,7 +38,7 @@ Status SingleSegmentSegmenter::FinalizeSegment(int64_t start_timestamp, if (muxer_listener()) { const uint64_t size = cluster()->Size(); muxer_listener()->OnNewSegment(options().output_file_name, start_timestamp, - duration_timestamp, size); + duration_timestamp, size, segment_number); } return Status::OK; } diff --git a/packager/media/formats/webm/single_segment_segmenter.h b/packager/media/formats/webm/single_segment_segmenter.h index 6f536463ce..b1ce5361cc 100644 --- a/packager/media/formats/webm/single_segment_segmenter.h +++ b/packager/media/formats/webm/single_segment_segmenter.h @@ -33,7 +33,8 @@ class SingleSegmentSegmenter : public Segmenter { /// @{ Status FinalizeSegment(int64_t start_timestamp, int64_t duration_timestamp, - bool is_subsegment) override; + bool is_subsegment, + int64_t segment_number) override; bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; std::vector GetSegmentRanges() override; diff --git a/packager/media/formats/webm/single_segment_segmenter_unittest.cc b/packager/media/formats/webm/single_segment_segmenter_unittest.cc index b3a30373ea..7bb47d592c 100644 --- a/packager/media/formats/webm/single_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/single_segment_segmenter_unittest.cc @@ -18,6 +18,8 @@ const int32_t kTimeScale = 1000000; const int32_t kTimecodeScale = 1000000; const int64_t kSecondsToNs = 1000000000L; const int64_t kDuration = 1000000; +const int64_t kSegmentNumber1 = 1; +const int64_t kSegmentNumber2 = 2; const bool kSubsegment = true; // clang-format off @@ -167,7 +169,8 @@ TEST_F(SingleSegmentSegmenterTest, BasicSupport) { CreateSample(kKeyFrame, kDuration, side_data_flag); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment, + kSegmentNumber1)); ASSERT_OK(segmenter_->Finalize()); ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData); @@ -180,14 +183,15 @@ TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegment) { // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { if (i == 5) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment, + kSegmentNumber1)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK( - segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, + !kSubsegment, kSegmentNumber2)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -205,13 +209,15 @@ TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) { // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { if (i == 5) { - ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment, + kSegmentNumber1)); } std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(*sample)); } - ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment)); + ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment, + kSegmentNumber2)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -238,7 +244,7 @@ TEST_F(SingleSegmentSegmenterTest, LargeTimestamp) { ASSERT_OK(segmenter_->AddSample(*sample)); } ASSERT_OK(segmenter_->FinalizeSegment(kLargeTimestamp, 5 * kDuration, - !kSubsegment)); + !kSubsegment, kSegmentNumber1)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -274,7 +280,7 @@ TEST_F(SingleSegmentSegmenterTest, ReallyLargeTimestamp) { ASSERT_OK(segmenter_->AddSample(*sample)); } ASSERT_OK(segmenter_->FinalizeSegment(kReallyLargeTimestamp, 5 * kDuration, - !kSubsegment)); + !kSubsegment, kSegmentNumber1)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. diff --git a/packager/media/formats/webm/webm_muxer.cc b/packager/media/formats/webm/webm_muxer.cc index 7af4dbd80e..1909b328db 100644 --- a/packager/media/formats/webm/webm_muxer.cc +++ b/packager/media/formats/webm/webm_muxer.cc @@ -82,9 +82,9 @@ Status WebMMuxer::FinalizeSegment(size_t stream_id, return Status(error::UNIMPLEMENTED, "Key rotation is not implemented for WebM"); } - return segmenter_->FinalizeSegment(segment_info.start_timestamp, - segment_info.duration, - segment_info.is_subsegment); + return segmenter_->FinalizeSegment( + segment_info.start_timestamp, segment_info.duration, + segment_info.is_subsegment, segment_info.segment_number); } void WebMMuxer::FireOnMediaStartEvent() { diff --git a/packager/media/formats/webvtt/webvtt_muxer_unittest.cc b/packager/media/formats/webvtt/webvtt_muxer_unittest.cc index a769ae3a57..38f0b5793f 100644 --- a/packager/media/formats/webvtt/webvtt_muxer_unittest.cc +++ b/packager/media/formats/webvtt/webvtt_muxer_unittest.cc @@ -39,6 +39,10 @@ const char* kSegmentedFileOutput1 = "memory://output/template-1.vtt"; const char* kSegmentedFileOutput2 = "memory://output/template-2.vtt"; const int64_t kSegmentDuration = 10000; + +const int64_t kSegmentNumber1 = 1; +const int64_t kSegmentNumber2 = 2; + const float kMillisecondsPerSecond = 1000.0f; } // namespace @@ -64,13 +68,14 @@ class WebVttMuxerTest : public MediaHandlerTestBase { }; TEST_F(WebVttMuxerTest, WithNoSegmentAndWithNoSamples) { - EXPECT_CALL(*muxer_listener_, OnNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*muxer_listener_, OnNewSegment(_, _, _, _, _)).Times(0); { // No segments should have be created as there were no samples. testing::InSequence s; EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); + EXPECT_CALL(*muxer_listener_, OnMediaEndMock(_, _, _, _, _, _, _, _, _)); } @@ -95,7 +100,7 @@ TEST_F(WebVttMuxerTest, WithOneSegmentAndWithOneSample) { EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput1, kSegmentStart, - kSegmentDuration, _)); + kSegmentDuration, _, _)); const float kMediaDuration = 1 * kSegmentDuration / kMillisecondsPerSecond; EXPECT_CALL(*muxer_listener_, @@ -112,8 +117,8 @@ TEST_F(WebVttMuxerTest, WithOneSegmentAndWithOneSample) { ASSERT_OK( Input(kInputIndex) ->Dispatch(StreamData::FromSegmentInfo( - kStreamIndex, - GetSegmentInfo(kSegmentStart, kSegmentDuration, !kEncrypted)))); + kStreamIndex, GetSegmentInfo(kSegmentStart, kSegmentDuration, + !kEncrypted, kSegmentNumber1)))); ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams()); ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput); @@ -142,10 +147,10 @@ TEST_F(WebVttMuxerTest, WithTwoSegmentAndWithOneSample) { EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput1, kSegment1Start, - kSegmentDuration, _)); + kSegmentDuration, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput2, kSegment2Start, - kSegmentDuration, _)); + kSegmentDuration, _, _)); const float kMediaDuration = 2 * kSegmentDuration / kMillisecondsPerSecond; EXPECT_CALL(*muxer_listener_, @@ -164,8 +169,8 @@ TEST_F(WebVttMuxerTest, WithTwoSegmentAndWithOneSample) { ASSERT_OK( Input(kInputIndex) ->Dispatch(StreamData::FromSegmentInfo( - kStreamIndex, - GetSegmentInfo(kSegment1Start, kSegmentDuration, !kEncrypted)))); + kStreamIndex, GetSegmentInfo(kSegment1Start, kSegmentDuration, + !kEncrypted, kSegmentNumber1)))); // Segment Two ASSERT_OK( Input(kInputIndex) @@ -174,8 +179,8 @@ TEST_F(WebVttMuxerTest, WithTwoSegmentAndWithOneSample) { ASSERT_OK( Input(kInputIndex) ->Dispatch(StreamData::FromSegmentInfo( - kStreamIndex, - GetSegmentInfo(kSegment2Start, kSegmentDuration, !kEncrypted)))); + kStreamIndex, GetSegmentInfo(kSegment2Start, kSegmentDuration, + !kEncrypted, kSegmentNumber2)))); ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams()); ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput1); @@ -202,10 +207,10 @@ TEST_F(WebVttMuxerTest, WithAnEmptySegment) { EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput1, kSegment1Start, - kSegmentDuration, _)); + kSegmentDuration, _, _)); EXPECT_CALL(*muxer_listener_, OnNewSegment(kSegmentedFileOutput2, kSegment2Start, - kSegmentDuration, _)); + kSegmentDuration, _, _)); const float kMediaDuration = 2 * kSegmentDuration / kMillisecondsPerSecond; EXPECT_CALL(*muxer_listener_, @@ -219,8 +224,8 @@ TEST_F(WebVttMuxerTest, WithAnEmptySegment) { ASSERT_OK( Input(kInputIndex) ->Dispatch(StreamData::FromSegmentInfo( - kStreamIndex, - GetSegmentInfo(kSegment1Start, kSegmentDuration, !kEncrypted)))); + kStreamIndex, GetSegmentInfo(kSegment1Start, kSegmentDuration, + !kEncrypted, kSegmentNumber1)))); // Segment Two ASSERT_OK( Input(kInputIndex) @@ -229,8 +234,8 @@ TEST_F(WebVttMuxerTest, WithAnEmptySegment) { ASSERT_OK( Input(kInputIndex) ->Dispatch(StreamData::FromSegmentInfo( - kStreamIndex, - GetSegmentInfo(kSegment2Start, kSegmentDuration, !kEncrypted)))); + kStreamIndex, GetSegmentInfo(kSegment2Start, kSegmentDuration, + !kEncrypted, kSegmentNumber2)))); ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams()); ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput1); diff --git a/packager/media/formats/webvtt/webvtt_to_mp4_handler_unittest.cc b/packager/media/formats/webvtt/webvtt_to_mp4_handler_unittest.cc index b42c107066..976ed2f610 100644 --- a/packager/media/formats/webvtt/webvtt_to_mp4_handler_unittest.cc +++ b/packager/media/formats/webvtt/webvtt_to_mp4_handler_unittest.cc @@ -23,6 +23,7 @@ namespace { const size_t kStreamIndex = 0; const bool kSubSegment = true; const bool kEncrypted = true; +const int64_t kSegmentNumber = 1; const char* kId1 = "sample-id-1"; const char* kId2 = "sample-id-2"; @@ -95,7 +96,8 @@ class WebVttToMp4HandlerTest : public MediaHandlerTestBase { const bool kIsSubSegment = true; int64_t duration = end_time - start_time; - auto segment = GetSegmentInfo(start_time, duration, !kIsSubSegment); + auto segment = + GetSegmentInfo(start_time, duration, !kIsSubSegment, kSegmentNumber); return In()->Dispatch( StreamData::FromSegmentInfo(kStreamIndex, std::move(segment))); } diff --git a/packager/media/trick_play/trick_play_handler_unittest.cc b/packager/media/trick_play/trick_play_handler_unittest.cc index aac1bd46b7..b27d0c1f7b 100644 --- a/packager/media/trick_play/trick_play_handler_unittest.cc +++ b/packager/media/trick_play/trick_play_handler_unittest.cc @@ -24,6 +24,7 @@ const size_t kOutputCount = 1; const size_t kInputIndex = 0; const size_t kOutputIndex = 0; const size_t kStreamIndex = 0; +const int64_t kSegmentNumber = 1; // This value does not matter as trick play does not use it, but it is needed // to create the audio and video info. @@ -54,7 +55,8 @@ class TrickPlayHandlerTest : public MediaHandlerTestBase { Status DispatchSegment(int64_t start_time, int64_t duration) { const bool kSubSegment = true; - auto info = GetSegmentInfo(start_time, duration, !kSubSegment); + auto info = + GetSegmentInfo(start_time, duration, !kSubSegment, kSegmentNumber); auto data = StreamData::FromSegmentInfo(kStreamIndex, std::move(info)); return Input(kInputIndex)->Dispatch(std::move(data)); } diff --git a/packager/mpd/base/adaptation_set_unittest.cc b/packager/mpd/base/adaptation_set_unittest.cc index b23b3fb4db..98150bf437 100644 --- a/packager/mpd/base/adaptation_set_unittest.cc +++ b/packager/mpd/base/adaptation_set_unittest.cc @@ -22,6 +22,7 @@ namespace shaka { namespace { const char kNoLanguage[] = ""; +const uint64_t kAnySegmentNumber = 1; } // namespace class AdaptationSetTest : public ::testing::Test { @@ -682,23 +683,25 @@ TEST_F(OnDemandAdaptationSetTest, SubsegmentAlignment) { adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); // Add a subsegment immediately before adding the 360p Representation. // This should still work for VOD. - representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize, + kAnySegmentNumber); Representation* representation_360p = adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize, + kAnySegmentNumber); auto aligned = adaptation_set->GetXml(); EXPECT_THAT(aligned, AttributeEqual("subsegmentAlignment", "true")); // Unknown because 480p has an extra subsegments. - representation_480p->AddNewSegment(11, 20, kAnySize); + representation_480p->AddNewSegment(11, 20, kAnySize, kAnySegmentNumber); auto alignment_unknown = adaptation_set->GetXml(); EXPECT_THAT(alignment_unknown, Not(AttributeSet("subsegmentAlignment"))); // Add segments that make them not aligned. - representation_360p->AddNewSegment(10, 1, kAnySize); - representation_360p->AddNewSegment(11, 19, kAnySize); + representation_360p->AddNewSegment(10, 1, kAnySize, kAnySegmentNumber); + representation_360p->AddNewSegment(11, 19, kAnySize, kAnySegmentNumber); auto unaligned = adaptation_set->GetXml(); EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment"))); @@ -740,8 +743,11 @@ TEST_F(OnDemandAdaptationSetTest, ForceSetsubsegmentAlignment) { static_assert(kStartTime1 != kStartTime2, "StartTimesShouldBeDifferent"); const int64_t kDuration = 10; const uint64_t kAnySize = 19834u; - representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize); - representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize); + + representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize, + kAnySegmentNumber); + representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize, + kAnySegmentNumber); auto unaligned = adaptation_set->GetXml(); EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment"))); @@ -791,15 +797,17 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignmentDynamicMpd) { Representation* representation_360p = adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); - representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize, + kAnySegmentNumber); + representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize, + kAnySegmentNumber); auto aligned = adaptation_set->GetXml(); EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true")); // Add segments that make them not aligned. - representation_480p->AddNewSegment(11, 20, kAnySize); - representation_360p->AddNewSegment(10, 1, kAnySize); - representation_360p->AddNewSegment(11, 19, kAnySize); + representation_480p->AddNewSegment(11, 20, kAnySize, kAnySegmentNumber); + representation_360p->AddNewSegment(10, 1, kAnySize, kAnySegmentNumber); + representation_360p->AddNewSegment(11, 19, kAnySize, kAnySegmentNumber); auto unaligned = adaptation_set->GetXml(); EXPECT_THAT(unaligned, Not(AttributeSet("segmentAlignment"))); @@ -844,16 +852,18 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignmentStaticMpd) { // Representation. Representation* representation_480p = adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); - representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize, + kAnySegmentNumber); Representation* representation_360p = adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); + representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize, + kAnySegmentNumber); representation_480p->AddNewSegment(kStartTime + kDuration, kDuration, - kAnySize); + kAnySize, kAnySegmentNumber); representation_360p->AddNewSegment(kStartTime + kDuration, kDuration, - kAnySize); + kAnySize, kAnySegmentNumber); auto aligned = adaptation_set->GetXml(); EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true")); diff --git a/packager/mpd/base/mock_mpd_builder.h b/packager/mpd/base/mock_mpd_builder.h index b771c300b7..0abc5bc380 100644 --- a/packager/mpd/base/mock_mpd_builder.h +++ b/packager/mpd/base/mock_mpd_builder.h @@ -75,8 +75,11 @@ class MockRepresentation : public Representation { void(const ContentProtectionElement& element)); MOCK_METHOD2(UpdateContentProtectionPssh, void(const std::string& drm_uuid, const std::string& pssh)); - MOCK_METHOD3(AddNewSegment, - void(int64_t start_time, int64_t duration, uint64_t size)); + MOCK_METHOD4(AddNewSegment, + void(int64_t start_time, + int64_t duration, + uint64_t size, + int64_t segment_number)); MOCK_METHOD0(SetSegmentDuration, void()); MOCK_METHOD0(SetAvailabilityTimeOffset, void()); MOCK_METHOD1(SetSampleDuration, void(int32_t sample_duration)); diff --git a/packager/mpd/base/mock_mpd_notifier.h b/packager/mpd/base/mock_mpd_notifier.h index 6d19413273..dcac171984 100644 --- a/packager/mpd/base/mock_mpd_notifier.h +++ b/packager/mpd/base/mock_mpd_notifier.h @@ -25,11 +25,12 @@ class MockMpdNotifier : public MpdNotifier { bool(const MediaInfo& media_info, uint32_t* container_id)); MOCK_METHOD2(NotifySampleDuration, bool(uint32_t container_id, int32_t sample_duration)); - MOCK_METHOD4(NotifyNewSegment, + MOCK_METHOD5(NotifyNewSegment, bool(uint32_t container_id, int64_t start_time, int64_t duration, - uint64_t size)); + uint64_t size, + int64_t segment_number)); MOCK_METHOD3(NotifyCompletedSegment, bool(uint32_t container_id, int64_t duration, uint64_t size)); MOCK_METHOD1(NotifyAvailabilityTimeOffset, bool(uint32_t container_id)); diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index 09e074f92b..367418bba4 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -56,6 +56,7 @@ class MpdBuilderTest : public ::testing::Test { // Not relevant in this test. const bool kContentProtectionFlag = true; const size_t kBytes = 1000; + const int64_t kSegmentNumber = 1; AdaptationSet* adaptation_set = period->GetOrCreateAdaptationSet(media_info, kContentProtectionFlag); @@ -63,7 +64,8 @@ class MpdBuilderTest : public ::testing::Test { adaptation_set->AddRepresentation(media_info); representation->AddNewSegment( segment_start_time_seconds * media_info.reference_time_scale(), - segment_duration_seconds * media_info.reference_time_scale(), kBytes); + segment_duration_seconds * media_info.reference_time_scale(), kBytes, + kSegmentNumber); } protected: diff --git a/packager/mpd/base/mpd_notifier.h b/packager/mpd/base/mpd_notifier.h index d75143899d..07f5e28bc0 100644 --- a/packager/mpd/base/mpd_notifier.h +++ b/packager/mpd/base/mpd_notifier.h @@ -87,11 +87,13 @@ class MpdNotifier { /// @param duration is the duration of the new segment, in units of the /// stream's time scale. /// @param size is the new segment size in bytes. + /// @param segment_number is the segment number. /// @return true on success, false otherwise. virtual bool NotifyNewSegment(uint32_t container_id, int64_t start_time, int64_t duration, - uint64_t size) = 0; + uint64_t size, + int64_t segment_number) = 0; /// Notifies MpdBuilder that a segment is fully written and provides the /// segment's complete duration and size. For Low Latency only. Note, size and diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc index 806c5b8ea5..bfd55c38d4 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -108,10 +108,6 @@ Representation::Representation( std::move(state_change_listener)) { mime_type_ = representation.mime_type_; codecs_ = representation.codecs_; - - start_number_ = representation.start_number_; - for (const SegmentInfo& segment_info : representation.segment_infos_) - start_number_ += segment_info.repeat + 1; } Representation::~Representation() {} @@ -172,7 +168,8 @@ void Representation::UpdateContentProtectionPssh(const std::string& drm_uuid, void Representation::AddNewSegment(int64_t start_time, int64_t duration, - uint64_t size) { + uint64_t size, + int64_t segment_number) { if (start_time == 0 && duration == 0) { LOG(WARNING) << "Got segment with start_time and duration == 0. Ignoring."; return; @@ -188,7 +185,7 @@ void Representation::AddNewSegment(int64_t start_time, if (state_change_listener_) state_change_listener_->OnNewSegmentForRepresentation(start_time, duration); - AddSegmentInfo(start_time, duration); + AddSegmentInfo(start_time, duration, segment_number); // Only update the buffer depth and bandwidth estimator when the full segment // is completed. In the low latency case, only the first chunk in the segment @@ -307,7 +304,7 @@ std::optional Representation::GetXml() { if (HasLiveOnlyFields(media_info_) && !representation.AddLiveOnlyInfo( - media_info_, segment_infos_, start_number_, + media_info_, segment_infos_, mpd_options_.mpd_params.low_latency_dash_mode)) { LOG(ERROR) << "Failed to add Live info."; return std::nullopt; @@ -383,7 +380,9 @@ bool Representation::HasRequiredMediaInfoFields() const { return true; } -void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) { +void Representation::AddSegmentInfo(int64_t start_time, + int64_t duration, + int64_t segment_number) { const uint64_t kNoRepeat = 0; const int64_t adjusted_duration = AdjustDuration(duration); @@ -406,7 +405,8 @@ void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) { } else { segment_infos_.push_back( {previous_segment_end_time, - actual_segment_end_time - previous_segment_end_time, kNoRepeat}); + actual_segment_end_time - previous_segment_end_time, kNoRepeat, + segment_number}); } return; } @@ -431,8 +431,8 @@ void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) { << previous_segment_end_time << "."; } } - - segment_infos_.push_back({start_time, adjusted_duration, kNoRepeat}); + segment_infos_.push_back( + {start_time, adjusted_duration, kNoRepeat, segment_number}); } void Representation::UpdateSegmentInfo(int64_t duration) { @@ -498,7 +498,6 @@ void Representation::SlideWindow() { current_buffer_depth_ - last->duration >= time_shift_buffer_depth) { current_buffer_depth_ -= last->duration; RemoveOldSegment(&*last); - start_number_++; } if (last->repeat >= 0) break; @@ -510,13 +509,15 @@ void Representation::RemoveOldSegment(SegmentInfo* segment_info) { int64_t segment_start_time = segment_info->start_time; segment_info->start_time += segment_info->duration; segment_info->repeat--; + int64_t start_number = segment_info->start_segment_number; + segment_info->start_segment_number++; if (mpd_options_.mpd_params.preserved_segments_outside_live_window == 0) return; segments_to_be_removed_.push_back( media::GetSegmentName(media_info_.segment_template(), segment_start_time, - start_number_ - 1, media_info_.bandwidth())); + start_number, media_info_.bandwidth())); while (segments_to_be_removed_.size() > mpd_options_.mpd_params.preserved_segments_outside_live_window) { VLOG(2) << "Deleting " << segments_to_be_removed_.front(); diff --git a/packager/mpd/base/representation.h b/packager/mpd/base/representation.h index e6b958bb43..3335b6b11d 100644 --- a/packager/mpd/base/representation.h +++ b/packager/mpd/base/representation.h @@ -99,9 +99,11 @@ class Representation { /// @param size of the segment in bytes. In the low latency case, this size is /// that of the /// first chunk because the full size is not yet known. + /// @param segment_number is the current segment number. virtual void AddNewSegment(int64_t start_time, int64_t duration, - uint64_t size); + uint64_t size, + int64_t segment_number); /// Update a media segment in the Representation. /// In the low latency case, the segment duration will not be ready until the @@ -198,7 +200,9 @@ class Representation { // Add a SegmentInfo. This function may insert an adjusted SegmentInfo if // |allow_approximate_segment_timeline_| is set. - void AddSegmentInfo(int64_t start_time, int64_t duration); + void AddSegmentInfo(int64_t start_time, + int64_t duration, + int64_t segment_number); // Update the current SegmentInfo. This method is used to update the duration // value after a low latency segment is complete, and the full segment @@ -248,10 +252,6 @@ class Representation { BandwidthEstimator bandwidth_estimator_; const MpdOptions& mpd_options_; - // startNumber attribute for SegmentTemplate. - // Starts from 1. - uint32_t start_number_ = 1; - // If this is not null, then Representation is responsible for calling the // right methods at right timings. std::unique_ptr state_change_listener_; diff --git a/packager/mpd/base/representation_unittest.cc b/packager/mpd/base/representation_unittest.cc index 66b6564048..e04af21c12 100644 --- a/packager/mpd/base/representation_unittest.cc +++ b/packager/mpd/base/representation_unittest.cc @@ -277,6 +277,8 @@ TEST_F(RepresentationTest, const int64_t kStartTime = 199238; const int64_t kDuration = 98; + const int64_t kSegmentNumber = 1; + std::unique_ptr listener( new MockRepresentationStateChangeListener()); EXPECT_CALL(*listener, OnNewSegmentForRepresentation(kStartTime, kDuration)); @@ -285,7 +287,8 @@ TEST_F(RepresentationTest, kAnyRepresentationId, std::move(listener)); EXPECT_TRUE(representation->Init()); - representation->AddNewSegment(kStartTime, kDuration, 10 /* any size */); + representation->AddNewSegment(kStartTime, kDuration, 10 /* any size */, + kSegmentNumber); } // Make sure @@ -463,7 +466,7 @@ class SegmentTemplateTest : public RepresentationTest { int repeat) { DCHECK(representation_); - SegmentInfo s = {start_time, duration, repeat}; + SegmentInfo s = {start_time, duration, repeat, segment_number_}; segment_infos_for_expected_out_.push_back(s); if (mpd_options_.mpd_params.low_latency_dash_mode) { @@ -471,7 +474,8 @@ class SegmentTemplateTest : public RepresentationTest { // At this point, only the first chunk of the low latency segment has been // written. The bandwidth will be updated once the segment is fully // written and the segment duration and size are known. - representation_->AddNewSegment(start_time, duration, size); + representation_->AddNewSegment(start_time, duration, size, + segment_number_++); return; } @@ -484,7 +488,8 @@ class SegmentTemplateTest : public RepresentationTest { } for (int i = 0; i < repeat + 1; ++i) { - representation_->AddNewSegment(start_time, duration, size); + representation_->AddNewSegment(start_time, duration, size, + segment_number_++); start_time += duration; bandwidth_estimator_.AddBlock( size, static_cast(duration) / kDefaultTimeScale); @@ -524,6 +529,7 @@ class SegmentTemplateTest : public RepresentationTest { std::list segment_infos_for_expected_out_; std::string expected_s_elements_; BandwidthEstimator bandwidth_estimator_; + int64_t segment_number_ = 1; }; // Estimate the bandwidth given the info from AddNewSegment(). @@ -584,12 +590,13 @@ TEST_F(SegmentTemplateTest, RepresentationClone) { auto cloned_representation = CopyRepresentation(*representation_, NoListener()); + const char kExpectedXml[] = "\n" " \n" + " media=\"$Number$.mp4\" startNumber=\"1\">\n" " \n" "\n"; EXPECT_THAT(cloned_representation->GetXml(), XmlNodeEqual(kExpectedXml)); diff --git a/packager/mpd/base/segment_info.h b/packager/mpd/base/segment_info.h index dc6415e279..a4772fa435 100644 --- a/packager/mpd/base/segment_info.h +++ b/packager/mpd/base/segment_info.h @@ -23,6 +23,7 @@ struct SegmentInfo { // |duration|, then this should be set to 0. The semantics is the same as S@r // in the DASH MPD spec. int repeat; + int64_t start_segment_number; }; } // namespace shaka diff --git a/packager/mpd/base/simple_mpd_notifier.cc b/packager/mpd/base/simple_mpd_notifier.cc index 4c096c5da9..585ad024dc 100644 --- a/packager/mpd/base/simple_mpd_notifier.cc +++ b/packager/mpd/base/simple_mpd_notifier.cc @@ -109,14 +109,15 @@ bool SimpleMpdNotifier::NotifySegmentDuration(uint32_t container_id) { bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id, int64_t start_time, int64_t duration, - uint64_t size) { + uint64_t size, + int64_t segment_number) { absl::MutexLock lock(&lock_); auto it = representation_map_.find(container_id); if (it == representation_map_.end()) { LOG(ERROR) << "Unexpected container_id: " << container_id; return false; } - it->second->AddNewSegment(start_time, duration, size); + it->second->AddNewSegment(start_time, duration, size, segment_number); return true; } diff --git a/packager/mpd/base/simple_mpd_notifier.h b/packager/mpd/base/simple_mpd_notifier.h index ad985aeb0f..49eefa3152 100644 --- a/packager/mpd/base/simple_mpd_notifier.h +++ b/packager/mpd/base/simple_mpd_notifier.h @@ -44,7 +44,8 @@ class SimpleMpdNotifier : public MpdNotifier { bool NotifyNewSegment(uint32_t container_id, int64_t start_time, int64_t duration, - uint64_t size) override; + uint64_t size, + int64_t segment_number) override; bool NotifyCompletedSegment(uint32_t container_id, int64_t duration, uint64_t size) override; diff --git a/packager/mpd/base/simple_mpd_notifier_unittest.cc b/packager/mpd/base/simple_mpd_notifier_unittest.cc index 426525da30..bed2ebefcc 100644 --- a/packager/mpd/base/simple_mpd_notifier_unittest.cc +++ b/packager/mpd/base/simple_mpd_notifier_unittest.cc @@ -218,6 +218,8 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewSegment) { SimpleMpdNotifier notifier(empty_mpd_option_); const uint32_t kRepresentationId = 447834u; + const int64_t kSegmentNumber1 = 1; + std::unique_ptr mock_mpd_builder(new MockMpdBuilder()); std::unique_ptr mock_representation( new MockRepresentation(kRepresentationId)); @@ -238,10 +240,12 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewSegment) { const int32_t kSegmentDuration = 100; const uint64_t kSegmentSize = 123456u; EXPECT_CALL(*mock_representation, - AddNewSegment(kStartTime, kSegmentDuration, kSegmentSize)); + AddNewSegment(kStartTime, kSegmentDuration, kSegmentSize, + kSegmentNumber1)); EXPECT_TRUE(notifier.NotifyNewSegment(kRepresentationId, kStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, + kSegmentNumber1)); } TEST_F(SimpleMpdNotifierTest, NotifyCueEvent) { diff --git a/packager/mpd/base/xml/xml_node.cc b/packager/mpd/base/xml/xml_node.cc index b054bc7fc6..6da265054b 100644 --- a/packager/mpd/base/xml/xml_node.cc +++ b/packager/mpd/base/xml/xml_node.cc @@ -475,9 +475,12 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info, bool RepresentationXmlNode::AddLiveOnlyInfo( const MediaInfo& media_info, const std::list& segment_infos, - uint32_t start_number, bool low_latency_dash_mode) { XmlNode segment_template("SegmentTemplate"); + + int start_number = + segment_infos.empty() ? 1 : segment_infos.begin()->start_segment_number; + if (media_info.has_reference_time_scale()) { RCHECK(segment_template.SetIntegerAttribute( "timescale", media_info.reference_time_scale())); diff --git a/packager/mpd/base/xml/xml_node.h b/packager/mpd/base/xml/xml_node.h index 766c475583..c73c0cbe82 100644 --- a/packager/mpd/base/xml/xml_node.h +++ b/packager/mpd/base/xml/xml_node.h @@ -220,7 +220,6 @@ class RepresentationXmlNode : public RepresentationBaseXmlNode { [[nodiscard]] bool AddLiveOnlyInfo( const MediaInfo& media_info, const std::list& segment_infos, - uint32_t start_number, bool low_latency_dash_mode); private: diff --git a/packager/mpd/base/xml/xml_node_unittest.cc b/packager/mpd/base/xml/xml_node_unittest.cc index 9b4f7254a3..20d8b2fb25 100644 --- a/packager/mpd/base/xml/xml_node_unittest.cc +++ b/packager/mpd/base/xml/xml_node_unittest.cc @@ -370,18 +370,18 @@ class LiveSegmentTimelineTest : public ::testing::Test { }; TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) { - const uint32_t kStartNumber = 1; + const uint32_t kSegmentNumber = 1; const int64_t kStartTime = 0; const int64_t kDuration = 100; const uint64_t kRepeat = 9; const bool kIsLowLatency = false; std::list segment_infos = { - {kStartTime, kDuration, kRepeat}, + {kStartTime, kDuration, kRepeat, kSegmentNumber}, }; RepresentationXmlNode representation; ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, - kStartNumber, kIsLowLatency)); + kIsLowLatency)); EXPECT_THAT( representation, @@ -392,18 +392,18 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) { } TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) { - const uint32_t kStartNumber = 1; const int64_t kNonZeroStartTime = 500; const int64_t kDuration = 100; + const uint32_t kSegmentNumber = 1; const uint64_t kRepeat = 9; const bool kIsLowLatency = false; std::list segment_infos = { - {kNonZeroStartTime, kDuration, kRepeat}, + {kNonZeroStartTime, kDuration, kRepeat, kSegmentNumber}, }; RepresentationXmlNode representation; ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, - kStartNumber, kIsLowLatency)); + kIsLowLatency)); EXPECT_THAT(representation, XmlNodeEqual( @@ -417,18 +417,18 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) { } TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) { - const uint32_t kStartNumber = 6; const int64_t kNonZeroStartTime = 500; const int64_t kDuration = 100; + const uint32_t kSegmentNumber = 6; const uint64_t kRepeat = 9; const bool kIsLowLatency = false; std::list segment_infos = { - {kNonZeroStartTime, kDuration, kRepeat}, + {kNonZeroStartTime, kDuration, kRepeat, kSegmentNumber}, }; RepresentationXmlNode representation; ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, - kStartNumber, kIsLowLatency)); + kIsLowLatency)); EXPECT_THAT( representation, @@ -439,8 +439,9 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) { } TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) { - const uint32_t kStartNumber = 1; const bool kIsLowLatency = false; + const uint32_t kSegmentNumber1 = 1; + const uint32_t kSegmentNumber11 = 11; const int64_t kStartTime1 = 0; const int64_t kDuration1 = 100; @@ -451,12 +452,12 @@ TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) { const uint64_t kRepeat2 = 0; std::list segment_infos = { - {kStartTime1, kDuration1, kRepeat1}, - {kStartTime2, kDuration2, kRepeat2}, + {kStartTime1, kDuration1, kRepeat1, kSegmentNumber1}, + {kStartTime2, kDuration2, kRepeat2, kSegmentNumber11}, }; RepresentationXmlNode representation; ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, - kStartNumber, kIsLowLatency)); + kIsLowLatency)); EXPECT_THAT( representation, @@ -467,8 +468,9 @@ TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) { } TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) { - const uint32_t kStartNumber = 1; const bool kIsLowLatency = false; + const uint32_t kSegmentNumber1 = 1; + const uint32_t kSegmentNumber11 = 11; const int64_t kStartTime1 = 0; const int64_t kDuration1 = 100; @@ -479,12 +481,12 @@ TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) { const uint64_t kRepeat2 = 1; std::list segment_infos = { - {kStartTime1, kDuration1, kRepeat1}, - {kStartTime2, kDuration2, kRepeat2}, + {kStartTime1, kDuration1, kRepeat1, kSegmentNumber1}, + {kStartTime2, kDuration2, kRepeat2, kSegmentNumber11}, }; RepresentationXmlNode representation; ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, - kStartNumber, kIsLowLatency)); + kIsLowLatency)); EXPECT_THAT(representation, XmlNodeEqual( @@ -499,8 +501,9 @@ TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) { } TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) { - const uint32_t kStartNumber = 1; const bool kIsLowLatency = false; + const uint32_t kSegmentNumber1 = 1; + const uint32_t kSegmentNumber11 = 11; const int64_t kStartTime1 = 0; const int64_t kDuration1 = 100; @@ -512,12 +515,12 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) { const uint64_t kRepeat2 = 0; std::list segment_infos = { - {kStartTime1, kDuration1, kRepeat1}, - {kStartTime2, kDuration2, kRepeat2}, + {kStartTime1, kDuration1, kRepeat1, kSegmentNumber1}, + {kStartTime2, kDuration2, kRepeat2, kSegmentNumber11}, }; RepresentationXmlNode representation; ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, - kStartNumber, kIsLowLatency)); + kIsLowLatency)); EXPECT_THAT(representation, XmlNodeEqual( @@ -532,14 +535,14 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) { } TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) { - const uint32_t kStartNumber = 1; const int64_t kStartTime = 0; const int64_t kDuration = 100; + const uint32_t kSegmentNumber = 1; const uint64_t kRepeat = 9; const bool kIsLowLatency = false; std::list segment_infos = { - {kStartTime, kDuration, kRepeat}, + {kStartTime, kDuration, kRepeat, kSegmentNumber}, }; RepresentationXmlNode representation; FlagSaver segment_number_saver( @@ -547,7 +550,7 @@ TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) { absl::SetFlag(&FLAGS_dash_add_last_segment_number_when_needed, true); ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, - kStartNumber, kIsLowLatency)); + kIsLowLatency)); EXPECT_THAT( representation, @@ -749,11 +752,11 @@ TEST_F(LowLatencySegmentTest, LowLatencySegmentTemplate) { const bool kIsLowLatency = true; std::list segment_infos = { - {kStartNumber, kDuration, kRepeat}, + {kStartNumber, kDuration, kRepeat, kStartNumber}, }; RepresentationXmlNode representation; ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, - kStartNumber, kIsLowLatency)); + kIsLowLatency)); EXPECT_THAT( representation, XmlNodeEqual("" diff --git a/packager/packager.cc b/packager/packager.cc index f0ff2c9348..dd575b6ef1 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -275,6 +275,11 @@ Status ValidateParams(const PackagingParams& packaging_params, "subsegment_sap_aligned to true is not allowed."); } + if (packaging_params.chunking_params.start_segment_number < 0) { + return Status(error::INVALID_ARGUMENT, + "Negative --start_segment_number is not allowed."); + } + if (stream_descriptors.empty()) { return Status(error::INVALID_ARGUMENT, "Stream descriptors cannot be empty."); @@ -517,8 +522,8 @@ std::unique_ptr CreateTextChunker( const ChunkingParams& chunking_params) { const float segment_length_in_seconds = chunking_params.segment_duration_in_seconds; - return std::unique_ptr( - new TextChunker(segment_length_in_seconds)); + return std::unique_ptr(new TextChunker( + segment_length_in_seconds, chunking_params.start_segment_number)); } Status CreateTtmlJobs(