feat: get start number from muxer and specify initial sequence number (#879)

Set the start number in representation to the segment index that is sent by muxer.

With this enhancement, you can now specify the initial sequence number
to be used on the generated segments when calling the packager.
With the old implementation, it was always starting with "1".

---------

Co-authored-by: Cosmin Stejerean <cstejerean@meta.com>
This commit is contained in:
sr90 2024-05-02 13:25:49 -07:00 committed by GitHub
parent 62f861c9c2
commit bb104fef5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
91 changed files with 536 additions and 357 deletions

View File

@ -21,3 +21,7 @@ Chunking options
Force fragments to begin with stream access points. This flag implies Force fragments to begin with stream access points. This flag implies
*segment_sap_aligned*. Default enabled. *segment_sap_aligned*. Default enabled.
--start_segment_number
Indicates the startNumber in DASH SegmentTemplate and HLS segment name.

View File

@ -31,6 +31,9 @@ struct ChunkingParams {
/// and mdat atom. Each chunk is uploaded immediately upon creation, /// and mdat atom. Each chunk is uploaded immediately upon creation,
/// decoupling latency from segment duration. /// decoupling latency from segment duration.
bool low_latency_dash_mode = false; bool low_latency_dash_mode = false;
/// Indicates the startNumber in DASH SegmentTemplate and HLS segment name.
int64_t start_segment_number = 1;
}; };
} // namespace shaka } // namespace shaka

View File

@ -73,3 +73,9 @@ ABSL_FLAG(
"If the first sample comes after default_text_zero_bias_ms then the start " "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 " "of the stream will not be padded as we cannot assume the start time of "
"the stream."); "the stream.");
ABSL_FLAG(int64_t,
start_segment_number,
1,
"Indicates the startNumber in DASH SegmentTemplate and HLS "
"segment name.");

View File

@ -22,5 +22,6 @@ ABSL_DECLARE_FLAG(std::string, temp_dir);
ABSL_DECLARE_FLAG(bool, mp4_include_pssh_in_stream); ABSL_DECLARE_FLAG(bool, mp4_include_pssh_in_stream);
ABSL_DECLARE_FLAG(int32_t, transport_stream_timestamp_offset_ms); ABSL_DECLARE_FLAG(int32_t, transport_stream_timestamp_offset_ms);
ABSL_DECLARE_FLAG(int32_t, default_text_zero_bias_ms); ABSL_DECLARE_FLAG(int32_t, default_text_zero_bias_ms);
ABSL_DECLARE_FLAG(int64_t, start_segment_number);
#endif // APP_MUXER_FLAGS_H_ #endif // APP_MUXER_FLAGS_H_

View File

@ -360,6 +360,8 @@ std::optional<PackagingParams> GetPackagingParams() {
absl::GetFlag(FLAGS_segment_sap_aligned); absl::GetFlag(FLAGS_segment_sap_aligned);
chunking_params.subsegment_sap_aligned = chunking_params.subsegment_sap_aligned =
absl::GetFlag(FLAGS_fragment_sap_aligned); absl::GetFlag(FLAGS_fragment_sap_aligned);
chunking_params.start_segment_number =
absl::GetFlag(FLAGS_start_segment_number);
int num_key_providers = 0; int num_key_providers = 0;
EncryptionParams& encryption_params = packaging_params.encryption_params; EncryptionParams& encryption_params = packaging_params.encryption_params;

View File

@ -485,8 +485,8 @@ class PackagerAppTest(unittest.TestCase):
use_fake_clock=True, use_fake_clock=True,
allow_codec_switching=False, allow_codec_switching=False,
dash_force_segment_list=False, dash_force_segment_list=False,
force_cl_index=None): force_cl_index=None,
start_segment_number=None):
flags = ['--single_threaded'] flags = ['--single_threaded']
if not strip_parameter_set_nalus: if not strip_parameter_set_nalus:
@ -575,6 +575,9 @@ class PackagerAppTest(unittest.TestCase):
elif force_cl_index is False: elif force_cl_index is False:
flags += ['--noforce_cl_index'] flags += ['--noforce_cl_index']
if start_segment_number is not None:
flags += ['--start_segment_number', str(start_segment_number)]
if ad_cues: if ad_cues:
flags += ['--ad_cues', ad_cues] flags += ['--ad_cues', ad_cues]
@ -823,6 +826,13 @@ class PackagerFunctionalTest(PackagerAppTest):
output_hls=True)) output_hls=True))
self._CheckTestResults('forced-subtitle') 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): def testAudioVideoWithLanguageOverride(self):
self.assertPackageSuccess( self.assertPackageSuccess(
self._GetStreams(['audio', 'video'], language='por', hls=True), self._GetStreams(['audio', 'video'], language='por', hls=True),

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-live:2011" minBufferTime="PT2S" type="dynamic" publishTime="some_time" availabilityStartTime="some_time" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S">
<Period id="0" start="PT0S">
<AdaptationSet id="0" contentType="audio" startWithSAP="1" segmentAlignment="true">
<Representation id="0" bandwidth="133961" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<SegmentTemplate timescale="44100" initialization="bear-640x360-audio-init.mp4" media="bear-640x360-audio-$Number$.m4s" startNumber="0">
<SegmentTimeline>
<S t="0" d="45056"/>
<S t="45056" d="44032"/>
<S t="89088" d="31744"/>
</SegmentTimeline>
</SegmentTemplate>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">
<Representation id="1" bandwidth="974154" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
<SegmentTemplate timescale="30000" initialization="bear-640x360-video-init.mp4" media="bear-640x360-video-$Number$.m4s" startNumber="0">
<SegmentTimeline>
<S t="0" d="30030" r="1"/>
<S t="60060" d="22022"/>
</SegmentTimeline>
</SegmentTemplate>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View File

@ -737,9 +737,9 @@ void MediaPlaylist::RemoveOldSegment(int64_t start_time) {
if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly)
return; return;
segments_to_be_removed_.push_back( segments_to_be_removed_.push_back(media::GetSegmentName(
media::GetSegmentName(media_info_.segment_template(), start_time, media_info_.segment_template(), start_time, media_sequence_number_ + 1,
media_sequence_number_, media_info_.bandwidth())); media_info_.bandwidth()));
while (segments_to_be_removed_.size() > while (segments_to_be_removed_.size() >
hls_params_.preserved_segments_outside_live_window) { hls_params_.preserved_segments_outside_live_window) {
VLOG(2) << "Deleting " << segments_to_be_removed_.front(); VLOG(2) << "Deleting " << segments_to_be_removed_.front();

View File

@ -59,6 +59,7 @@ struct SegmentInfo {
bool is_encrypted = false; bool is_encrypted = false;
int64_t start_timestamp = -1; int64_t start_timestamp = -1;
int64_t duration = 0; int64_t duration = 0;
int64_t segment_number = 1;
// This is only available if key rotation is enabled. Note that we may have // 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, // a |key_rotation_encryption_config| even if the segment is not encrypted,
// which is the case for clear lead. // which is the case for clear lead.

View File

@ -251,11 +251,13 @@ std::shared_ptr<MediaSample> MediaHandlerTestBase::GetMediaSample(
std::unique_ptr<SegmentInfo> MediaHandlerTestBase::GetSegmentInfo( std::unique_ptr<SegmentInfo> MediaHandlerTestBase::GetSegmentInfo(
int64_t start_timestamp, int64_t start_timestamp,
int64_t duration, int64_t duration,
bool is_subsegment) const { bool is_subsegment,
int64_t segment_number) const {
std::unique_ptr<SegmentInfo> info(new SegmentInfo); std::unique_ptr<SegmentInfo> info(new SegmentInfo);
info->start_timestamp = start_timestamp; info->start_timestamp = start_timestamp;
info->duration = duration; info->duration = duration;
info->is_subsegment = is_subsegment; info->is_subsegment = is_subsegment;
info->segment_number = segment_number;
return info; return info;
} }

View File

@ -325,7 +325,8 @@ class MediaHandlerTestBase : public ::testing::Test {
std::unique_ptr<SegmentInfo> GetSegmentInfo(int64_t start_timestamp, std::unique_ptr<SegmentInfo> GetSegmentInfo(int64_t start_timestamp,
int64_t duration, int64_t duration,
bool is_subsegment) const; bool is_subsegment,
int64_t segment_number) const;
std::unique_ptr<StreamInfo> GetTextStreamInfo(int32_t timescale) const; std::unique_ptr<StreamInfo> GetTextStreamInfo(int32_t timescale) const;

View File

@ -117,7 +117,7 @@ class Muxer : public MediaHandler {
// In VOD single segment case with Ad Cues, |output_file_name| is allowed to // 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. // be a template. In this case, there will be NumAdCues + 1 files generated.
std::string output_file_template_; std::string output_file_template_;
size_t output_file_index_ = 0; size_t output_file_index_ = 1;
}; };
} // namespace media } // namespace media

View File

@ -110,7 +110,7 @@ Status ValidateSegmentTemplate(const std::string& segment_template) {
std::string GetSegmentName(const std::string& segment_template, std::string GetSegmentName(const std::string& segment_template,
int64_t segment_start_time, int64_t segment_start_time,
uint32_t segment_index, uint32_t segment_number,
uint32_t bandwidth) { uint32_t bandwidth) {
DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template)); DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template));
@ -154,7 +154,7 @@ std::string GetSegmentName(const std::string& segment_template,
absl::UntypedFormatSpec format(format_tag); absl::UntypedFormatSpec format(format_tag);
if (identifier == "Number") { if (identifier == "Number") {
// SegmentNumber starts from 1. // SegmentNumber starts from 1.
format_args.emplace_back(static_cast<uint64_t>(segment_index + 1)); format_args.emplace_back(static_cast<uint64_t>(segment_number));
} else if (identifier == "Time") { } else if (identifier == "Time") {
format_args.emplace_back(static_cast<uint64_t>(segment_start_time)); format_args.emplace_back(static_cast<uint64_t>(segment_start_time));
} else if (identifier == "Bandwidth") { } else if (identifier == "Bandwidth") {

View File

@ -29,12 +29,12 @@ Status ValidateSegmentTemplate(const std::string& segment_template);
/// @param segment_template is the segment template pattern, which should /// @param segment_template is the segment template pattern, which should
/// comply with ISO/IEC 23009-1:2012 5.3.9.4.4. /// comply with ISO/IEC 23009-1:2012 5.3.9.4.4.
/// @param segment_start_time specifies the segment start time. /// @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. /// @param bandwidth represents the bit rate, in bits/sec, of the stream.
/// @return The segment name with identifier substituted. /// @return The segment name with identifier substituted.
std::string GetSegmentName(const std::string& segment_template, std::string GetSegmentName(const std::string& segment_template,
int64_t segment_start_time, int64_t segment_start_time,
uint32_t segment_index, uint32_t segment_number,
uint32_t bandwidth); uint32_t bandwidth);
} // namespace media } // namespace media

View File

@ -62,95 +62,57 @@ TEST(MuxerUtilTest, ValidateSegmentTemplateWithFormatTag) {
TEST(MuxerUtilTest, GetSegmentName) { TEST(MuxerUtilTest, GetSegmentName) {
const int64_t kSegmentStartTime = 180180; const int64_t kSegmentStartTime = 180180;
const uint32_t kSegmentIndex = 11; const uint32_t kSegmentNumber = 12;
const uint32_t kBandwidth = 1234; const uint32_t kBandwidth = 1234;
EXPECT_EQ("12", GetSegmentName("$Number$", EXPECT_EQ("12", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber,
kSegmentStartTime,
kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("012", EXPECT_EQ("012", GetSegmentName("$Number%03d$", kSegmentStartTime,
GetSegmentName("$Number%03d$", kSegmentNumber, kBandwidth));
kSegmentStartTime, EXPECT_EQ("12$foo$00012",
kSegmentIndex, GetSegmentName("$Number%01d$$$foo$$$Number%05d$", kSegmentStartTime,
kBandwidth)); kSegmentNumber, kBandwidth));
EXPECT_EQ(
"12$foo$00012",
GetSegmentName(
"$Number%01d$$$foo$$$Number%05d$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("180180", EXPECT_EQ("180180", GetSegmentName("$Time$", kSegmentStartTime,
GetSegmentName("$Time$", kSegmentNumber, kBandwidth));
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("foo$_$18018000180180.m4s", EXPECT_EQ("foo$_$18018000180180.m4s",
GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s", GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s",
kSegmentStartTime, kSegmentStartTime, kSegmentNumber, kBandwidth));
kSegmentIndex,
kBandwidth));
// Combo values. // Combo values.
EXPECT_EQ("12-1234", EXPECT_EQ("12-1234", GetSegmentName("$Number$-$Bandwidth$", kSegmentStartTime,
GetSegmentName("$Number$-$Bandwidth$", kSegmentNumber, kBandwidth));
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("012-001234", EXPECT_EQ("012-001234",
GetSegmentName("$Number%03d$-$Bandwidth%06d$", GetSegmentName("$Number%03d$-$Bandwidth%06d$", kSegmentStartTime,
kSegmentStartTime, kSegmentNumber, kBandwidth));
kSegmentIndex,
kBandwidth));
// Format specifier edge cases. // Format specifier edge cases.
EXPECT_EQ("12", EXPECT_EQ("12", GetSegmentName("$Number%00d$", kSegmentStartTime,
GetSegmentName("$Number%00d$", kSegmentNumber, kBandwidth));
kSegmentStartTime, EXPECT_EQ("00012", GetSegmentName("$Number%005d$", kSegmentStartTime,
kSegmentIndex, kSegmentNumber, kBandwidth));
kBandwidth));
EXPECT_EQ("00012",
GetSegmentName("$Number%005d$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
} }
TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) { TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) {
const int64_t kSegmentStartTime = 0; const int64_t kSegmentStartTime = 0;
const uint32_t kSegmentIndex = 0; const uint32_t kSegmentNumber = 1;
const uint32_t kBandwidth = 0; const uint32_t kBandwidth = 0;
EXPECT_EQ("1", GetSegmentName("$Number$", EXPECT_EQ("1", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber,
kSegmentStartTime,
kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("001", EXPECT_EQ("001", GetSegmentName("$Number%03d$", kSegmentStartTime,
GetSegmentName("$Number%03d$", kSegmentNumber, kBandwidth));
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("0", GetSegmentName("$Time$", EXPECT_EQ("0", GetSegmentName("$Time$", kSegmentStartTime, kSegmentNumber,
kSegmentStartTime,
kSegmentIndex,
kBandwidth)); kBandwidth));
EXPECT_EQ("00000000.m4s", EXPECT_EQ("00000000.m4s", GetSegmentName("$Time%08d$.m4s", kSegmentStartTime,
GetSegmentName("$Time%08d$.m4s", kSegmentNumber, kBandwidth));
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
} }
TEST(MuxerUtilTest, GetSegmentNameLargeTime) { TEST(MuxerUtilTest, GetSegmentNameLargeTime) {
const int64_t kSegmentStartTime = 1601599839840ULL; const int64_t kSegmentStartTime = 1601599839840ULL;
const uint32_t kSegmentIndex = 8888888; const uint32_t kSegmentNumber = 8888889;
const uint32_t kBandwidth = 444444; const uint32_t kBandwidth = 444444;
EXPECT_EQ("1601599839840", EXPECT_EQ("1601599839840", GetSegmentName("$Time$", kSegmentStartTime,
GetSegmentName("$Time$", kSegmentNumber, kBandwidth));
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
} }
} // namespace media } // namespace media

View File

@ -50,9 +50,13 @@ Status TextMuxer::Finalize() {
// Insert a dummy value so the HLS generator will generate a segment list. // Insert a dummy value so the HLS generator will generate a segment list.
ranges.subsegment_ranges.emplace_back(); ranges.subsegment_ranges.emplace_back();
// The segment number does not matter for single segment output.
const uint32_t kArbitrarySegmentNumber = 0;
muxer_listener()->OnNewSegment( muxer_listener()->OnNewSegment(
options().output_file_name, 0, 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); 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; const std::string& segment_template = options().segment_template;
DCHECK(!segment_template.empty()); DCHECK(!segment_template.empty());
const uint32_t index = segment_index_++;
const int64_t start = segment_info.start_timestamp; const int64_t start = segment_info.start_timestamp;
const int64_t duration = segment_info.duration; const int64_t duration = segment_info.duration;
const uint32_t segment_number = segment_info.segment_number;
const uint32_t bandwidth = options().bandwidth; const uint32_t bandwidth = options().bandwidth;
const std::string filename = const std::string filename =
GetSegmentName(segment_template, start, index, bandwidth); GetSegmentName(segment_template, start, segment_number, bandwidth);
uint64_t size; uint64_t size;
RETURN_IF_ERROR(WriteToFile(filename, &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; return Status::OK;
} }

View File

@ -38,7 +38,6 @@ class TextMuxer : public Muxer {
int64_t total_duration_ms_ = 0; int64_t total_duration_ms_ = 0;
int64_t last_cue_ms_ = 0; int64_t last_cue_ms_ = 0;
uint32_t segment_index_ = 0;
}; };
} // namespace media } // namespace media

View File

@ -34,6 +34,7 @@ bool IsNewSegmentIndex(int64_t new_index, int64_t current_index) {
ChunkingHandler::ChunkingHandler(const ChunkingParams& chunking_params) ChunkingHandler::ChunkingHandler(const ChunkingParams& chunking_params)
: chunking_params_(chunking_params) { : chunking_params_(chunking_params) {
CHECK_NE(chunking_params.segment_duration_in_seconds, 0u); CHECK_NE(chunking_params.segment_duration_in_seconds, 0u);
segment_number_ = chunking_params.start_segment_number;
} }
Status ChunkingHandler::InitializeInternal() { Status ChunkingHandler::InitializeInternal() {
@ -163,17 +164,20 @@ Status ChunkingHandler::OnMediaSample(
return DispatchMediaSample(kStreamIndex, std::move(sample)); return DispatchMediaSample(kStreamIndex, std::move(sample));
} }
Status ChunkingHandler::EndSegmentIfStarted() const { Status ChunkingHandler::EndSegmentIfStarted() {
if (!segment_start_time_) if (!segment_start_time_)
return Status::OK; return Status::OK;
auto segment_info = std::make_shared<SegmentInfo>(); auto segment_info = std::make_shared<SegmentInfo>();
segment_info->start_timestamp = segment_start_time_.value(); segment_info->start_timestamp = segment_start_time_.value();
segment_info->duration = max_segment_time_ - 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) { if (chunking_params_.low_latency_dash_mode) {
segment_info->is_chunk = true; segment_info->is_chunk = true;
segment_info->is_final_chunk_in_seg = true; segment_info->is_final_chunk_in_seg = true;
} }
return DispatchSegmentInfo(kStreamIndex, std::move(segment_info)); return DispatchSegmentInfo(kStreamIndex, std::move(segment_info));
} }

View File

@ -60,7 +60,7 @@ class ChunkingHandler : public MediaHandler {
Status OnCueEvent(std::shared_ptr<const CueEvent> event); Status OnCueEvent(std::shared_ptr<const CueEvent> event);
Status OnMediaSample(std::shared_ptr<const MediaSample> sample); Status OnMediaSample(std::shared_ptr<const MediaSample> sample);
Status EndSegmentIfStarted() const; Status EndSegmentIfStarted();
Status EndSubsegmentIfStarted() const; Status EndSubsegmentIfStarted() const;
bool IsSubsegmentEnabled() { bool IsSubsegmentEnabled() {
@ -74,8 +74,13 @@ class ChunkingHandler : public MediaHandler {
int64_t segment_duration_ = 0; int64_t segment_duration_ = 0;
int64_t subsegment_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. // Current segment index, useful to determine where to do chunking.
int64_t current_segment_index_ = -1; int64_t current_segment_index_ = -1;
// Current subsegment index, useful to determine where to do chunking. // Current subsegment index, useful to determine where to do chunking.
int64_t current_subsegment_index_ = -1; int64_t current_subsegment_index_ = -1;

View File

@ -16,8 +16,10 @@ namespace {
const size_t kStreamIndex = 0; const size_t kStreamIndex = 0;
} // namespace } // namespace
TextChunker::TextChunker(double segment_duration_in_seconds) TextChunker::TextChunker(double segment_duration_in_seconds,
: segment_duration_in_seconds_(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<StreamData> data) { Status TextChunker::Process(std::unique_ptr<StreamData> data) {
switch (data->stream_data_type) { switch (data->stream_data_type) {
@ -60,14 +62,12 @@ Status TextChunker::OnCueEvent(std::shared_ptr<const CueEvent> event) {
// Convert the event's time to be scaled to the time of each sample. // Convert the event's time to be scaled to the time of each sample.
const int64_t event_time = ScaleTime(event->time_in_seconds); const int64_t event_time = ScaleTime(event->time_in_seconds);
// Output all full segments before the segment that the cue event interupts. // Output all full segments before the segment that the cue event interupts.
while (segment_start_ + segment_duration_ < event_time) { while (segment_start_ + segment_duration_ < event_time) {
RETURN_IF_ERROR(DispatchSegment(segment_duration_)); RETURN_IF_ERROR(DispatchSegment(segment_duration_));
} }
const int64_t shorten_duration = event_time - segment_start_; const int64_t shorten_duration = event_time - segment_start_;
RETURN_IF_ERROR(DispatchSegment(shorten_duration)); RETURN_IF_ERROR(DispatchSegment(shorten_duration));
return DispatchCueEvent(kStreamIndex, std::move(event)); return DispatchCueEvent(kStreamIndex, std::move(event));
} }
@ -109,6 +109,8 @@ Status TextChunker::DispatchSegment(int64_t duration) {
std::shared_ptr<SegmentInfo> info = std::make_shared<SegmentInfo>(); std::shared_ptr<SegmentInfo> info = std::make_shared<SegmentInfo>();
info->start_timestamp = segment_start_; info->start_timestamp = segment_start_;
info->duration = duration; info->duration = duration;
info->segment_number = segment_number_++;
RETURN_IF_ERROR(DispatchSegmentInfo(kStreamIndex, std::move(info))); RETURN_IF_ERROR(DispatchSegmentInfo(kStreamIndex, std::move(info)));
// Move onto the next segment. // Move onto the next segment.

View File

@ -20,7 +20,8 @@ namespace media {
// is when a cue event is seen. // is when a cue event is seen.
class TextChunker : public MediaHandler { class TextChunker : public MediaHandler {
public: public:
explicit TextChunker(double segment_duration_in_seconds); explicit TextChunker(double segment_duration_in_seconds,
int64_t start_segment_number);
private: private:
TextChunker(const TextChunker&) = delete; 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_start_ = -1; // Set when the first sample comes in.
int64_t segment_duration_ = -1; // Set in OnStreamInfo. 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 // 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 // until the segment ends because a cue event may end the segment sooner
// than we expected. // than we expected.

View File

@ -30,6 +30,8 @@ const size_t kOutput = 0;
const bool kEncrypted = true; const bool kEncrypted = true;
const bool kSubSegment = true; const bool kSubSegment = true;
const int64_t kStartSegmentNumber = 1;
const char* kNoId = ""; const char* kNoId = "";
const char* kNoPayload = ""; const char* kNoPayload = "";
} // namespace } // namespace
@ -38,7 +40,8 @@ class TextChunkerTest : public MediaHandlerTestBase {
protected: protected:
Status Init(double segment_duration) { Status Init(double segment_duration) {
return SetUpAndInitializeGraph( return SetUpAndInitializeGraph(
std::make_shared<TextChunker>(segment_duration), kInputs, kOutputs); std::make_shared<TextChunker>(segment_duration, kStartSegmentNumber),
kInputs, kOutputs);
} }
}; };

View File

@ -357,7 +357,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithNoKeyRotation) {
kIsKeyFrame, kData, kDataSize)))); kIsKeyFrame, kData, kDataSize))));
ASSERT_OK(Process(StreamData::FromSegmentInfo( ASSERT_OK(Process(StreamData::FromSegmentInfo(
kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration, kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration,
!kIsSubsegment)))); !kIsSubsegment, i + 1))));
const bool is_encrypted = i == 2; const bool is_encrypted = i == 2;
const auto& output_stream_data = GetOutputStreamDataVector(); const auto& output_stream_data = GetOutputStreamDataVector();
EXPECT_THAT(output_stream_data, EXPECT_THAT(output_stream_data,
@ -436,7 +436,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) {
kIsKeyFrame, kData, kDataSize)))); kIsKeyFrame, kData, kDataSize))));
ASSERT_OK(Process(StreamData::FromSegmentInfo( ASSERT_OK(Process(StreamData::FromSegmentInfo(
kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration, kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration,
!kIsSubsegment)))); !kIsSubsegment, i))));
const bool is_encrypted = i >= 2; const bool is_encrypted = i >= 2;
const auto& output_stream_data = GetOutputStreamDataVector(); const auto& output_stream_data = GetOutputStreamDataVector();
EXPECT_THAT(output_stream_data, EXPECT_THAT(output_stream_data,

View File

@ -71,9 +71,11 @@ void CombinedMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
void CombinedMuxerListener::OnNewSegment(const std::string& file_name, void CombinedMuxerListener::OnNewSegment(const std::string& file_name,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t segment_file_size) { uint64_t segment_file_size,
int64_t segment_number) {
for (auto& listener : muxer_listeners_) { 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);
} }
} }

View File

@ -44,7 +44,8 @@ class CombinedMuxerListener : public MuxerListener {
void OnNewSegment(const std::string& file_name, void OnNewSegment(const std::string& file_name,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t segment_file_size) override; uint64_t segment_file_size,
int64_t segment_number) override;
void OnCompletedSegment(int64_t duration, void OnCompletedSegment(int64_t duration,
uint64_t segment_file_size) override; uint64_t segment_file_size) override;
void OnKeyFrame(int64_t timestamp, void OnKeyFrame(int64_t timestamp,

View File

@ -20,6 +20,7 @@ struct SegmentEventInfo {
// The below two fields are only useful for Segment. // The below two fields are only useful for Segment.
int64_t duration; int64_t duration;
uint64_t segment_file_size; uint64_t segment_file_size;
int64_t segment_number;
}; };
struct KeyFrameEvent { struct KeyFrameEvent {

View File

@ -244,11 +244,13 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t segment_file_size) { uint64_t segment_file_size,
int64_t segment_number) {
if (!media_info_->has_segment_template()) { if (!media_info_->has_segment_template()) {
EventInfo event_info; EventInfo event_info;
event_info.type = EventInfoType::kSegment; 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); event_info_.push_back(event_info);
} else { } else {
// For multisegment, it always starts from the beginning of the file. // For multisegment, it always starts from the beginning of the file.

View File

@ -72,7 +72,8 @@ class HlsNotifyMuxerListener : public MuxerListener {
void OnNewSegment(const std::string& file_name, void OnNewSegment(const std::string& file_name,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t segment_file_size) override; uint64_t segment_file_size,
int64_t segment_number) override;
void OnKeyFrame(int64_t timestamp, void OnKeyFrame(int64_t timestamp,
uint64_t start_byte_offset, uint64_t start_byte_offset,
uint64_t size) override; uint64_t size) override;

View File

@ -79,6 +79,7 @@ const uint64_t kSegmentStartOffset = 10000;
const int64_t kSegmentStartTime = 19283; const int64_t kSegmentStartTime = 19283;
const int64_t kSegmentDuration = 98028; const int64_t kSegmentDuration = 98028;
const uint64_t kSegmentSize = 756739; const uint64_t kSegmentSize = 756739;
const int64_t kAnySegmentNumber = 10;
const int64_t kCueStartTime = kSegmentStartTime; const int64_t kCueStartTime = kSegmentStartTime;
@ -349,7 +350,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) {
kSegmentDuration, _, kSegmentSize)); kSegmentDuration, _, kSegmentSize));
listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnCueEvent(kCueStartTime, "dummy cue data");
listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime,
kSegmentDuration, kSegmentSize); kSegmentDuration, kSegmentSize, kAnySegmentNumber);
} }
// Verify that the notifier is called for every segment in OnMediaEnd if // 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_.OnCueEvent(kCueStartTime, "dummy cue data");
listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration,
kSegmentSize); kSegmentSize, kAnySegmentNumber);
EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kCueStartTime)); EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kCueStartTime));
EXPECT_CALL( EXPECT_CALL(
@ -397,7 +398,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) {
listener_.OnMediaStart(muxer_options1, *video_stream_info, 90000, listener_.OnMediaStart(muxer_options1, *video_stream_info, 90000,
MuxerListener::kContainerMpeg2ts); MuxerListener::kContainerMpeg2ts);
listener_.OnNewSegment("filename1.mp4", kSegmentStartTime, kSegmentDuration, listener_.OnNewSegment("filename1.mp4", kSegmentStartTime, kSegmentDuration,
kSegmentSize); kSegmentSize, kAnySegmentNumber);
listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnCueEvent(kCueStartTime, "dummy cue data");
EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _))
@ -414,7 +415,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) {
listener_.OnMediaStart(muxer_options2, *video_stream_info, 90000, listener_.OnMediaStart(muxer_options2, *video_stream_info, 90000,
MuxerListener::kContainerMpeg2ts); MuxerListener::kContainerMpeg2ts);
listener_.OnNewSegment("filename2.mp4", kSegmentStartTime + kSegmentDuration, listener_.OnNewSegment("filename2.mp4", kSegmentStartTime + kSegmentDuration,
kSegmentDuration, kSegmentSize); kSegmentDuration, kSegmentSize, kAnySegmentNumber);
EXPECT_CALL(mock_notifier_, EXPECT_CALL(mock_notifier_,
NotifyNewSegment(_, StrEq("filename2.mp4"), NotifyNewSegment(_, StrEq("filename2.mp4"),
kSegmentStartTime + kSegmentDuration, _, _, _)); kSegmentStartTime + kSegmentDuration, _, _, _));
@ -440,7 +441,7 @@ TEST_F(HlsNotifyMuxerListenerTest,
MuxerListener::kContainerMpeg2ts); MuxerListener::kContainerMpeg2ts);
listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration,
kSegmentSize); kSegmentSize, kAnySegmentNumber);
EXPECT_CALL( EXPECT_CALL(
mock_notifier_, mock_notifier_,
NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime, NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime,
@ -504,7 +505,7 @@ TEST_P(HlsNotifyMuxerListenerKeyFrameTest, NoSegmentTemplate) {
listener_.OnKeyFrame(kKeyFrameTimestamp, kKeyFrameStartByteOffset, listener_.OnKeyFrame(kKeyFrameTimestamp, kKeyFrameStartByteOffset,
kKeyFrameSize); kKeyFrameSize);
listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration,
kSegmentSize); kSegmentSize, kAnySegmentNumber);
EXPECT_CALL(mock_notifier_, EXPECT_CALL(mock_notifier_,
NotifyKeyFrame(_, kKeyFrameTimestamp, NotifyKeyFrame(_, kKeyFrameTimestamp,

View File

@ -56,11 +56,12 @@ class MockMuxerListener : public MuxerListener {
void OnMediaEnd(const MediaRanges& range, void OnMediaEnd(const MediaRanges& range,
float duration_seconds) override; float duration_seconds) override;
MOCK_METHOD4(OnNewSegment, MOCK_METHOD5(OnNewSegment,
void(const std::string& segment_name, void(const std::string& segment_name,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t segment_file_size)); uint64_t segment_file_size,
int64_t segment_number));
MOCK_METHOD3(OnKeyFrame, MOCK_METHOD3(OnKeyFrame,
void(int64_t timestamp, void(int64_t timestamp,

View File

@ -182,7 +182,8 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
mpd_notifier_->NotifyNewSegment( mpd_notifier_->NotifyNewSegment(
notification_id_.value(), event_info.segment_info.start_time, notification_id_.value(), event_info.segment_info.start_time,
event_info.segment_info.duration, 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; break;
case EventInfoType::kKeyFrame: case EventInfoType::kKeyFrame:
// NO-OP for DASH. // NO-OP for DASH.
@ -200,17 +201,20 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name, void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t segment_file_size) { uint64_t segment_file_size,
int64_t segment_number) {
UNUSED(file_name); UNUSED(file_name);
if (mpd_notifier_->dash_profile() == DashProfile::kLive) { if (mpd_notifier_->dash_profile() == DashProfile::kLive) {
mpd_notifier_->NotifyNewSegment(notification_id_.value(), start_time, 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) if (mpd_notifier_->mpd_type() == MpdType::kDynamic)
mpd_notifier_->Flush(); mpd_notifier_->Flush();
} else { } else {
EventInfo event_info; EventInfo event_info;
event_info.type = EventInfoType::kSegment; 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); event_info_.push_back(event_info);
} }
} }

View File

@ -52,7 +52,8 @@ class MpdNotifyMuxerListener : public MuxerListener {
void OnNewSegment(const std::string& file_name, void OnNewSegment(const std::string& file_name,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t segment_file_size) override; uint64_t segment_file_size,
int64_t segment_number) override;
void OnCompletedSegment(int64_t duration, void OnCompletedSegment(int64_t duration,
uint64_t segment_file_size) override; uint64_t segment_file_size) override;
void OnKeyFrame(int64_t timestamp, void OnKeyFrame(int64_t timestamp,

View File

@ -397,27 +397,31 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) {
const uint64_t kSegmentFileSize1 = 29812u; const uint64_t kSegmentFileSize1 = 29812u;
const int64_t kStartTime2 = 1001; const int64_t kStartTime2 = 1001;
const int64_t kDuration2 = 3787; const int64_t kDuration2 = 3787;
const int64_t kSegmentNumber1 = 1;
const uint64_t kSegmentFileSize2 = 83743u; const uint64_t kSegmentFileSize2 = 83743u;
const int64_t kSegmentNumber2 = 2;
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); 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, listener_->OnMediaStart(muxer_options, *video_stream_info,
kDefaultReferenceTimeScale, kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1,
kSegmentNumber1);
listener_->OnCueEvent(kStartTime2, "dummy cue data"); listener_->OnCueEvent(kStartTime2, "dummy cue data");
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2,
kSegmentNumber2);
::testing::Mock::VerifyAndClearExpectations(notifier_.get()); ::testing::Mock::VerifyAndClearExpectations(notifier_.get());
InSequence s; InSequence s;
EXPECT_CALL(*notifier_, NotifyNewContainer( EXPECT_CALL(*notifier_, NotifyNewContainer(
ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)) ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1,
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); kSegmentFileSize1, kSegmentNumber1));
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); kSegmentFileSize2, kSegmentNumber2));
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
} }
@ -433,29 +437,33 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegmentSegmentList) {
const int64_t kStartTime1 = 0; const int64_t kStartTime1 = 0;
const int64_t kDuration1 = 1000; const int64_t kDuration1 = 1000;
const uint64_t kSegmentFileSize1 = 29812u; const uint64_t kSegmentFileSize1 = 29812u;
const int64_t kSegmentNumber1 = 1;
const int64_t kStartTime2 = 1001; const int64_t kStartTime2 = 1001;
const int64_t kDuration2 = 3787; const int64_t kDuration2 = 3787;
const uint64_t kSegmentFileSize2 = 83743u; const uint64_t kSegmentFileSize2 = 83743u;
const int64_t kSegmentNumber2 = 2;
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); 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, listener_->OnMediaStart(muxer_options, *video_stream_info,
kDefaultReferenceTimeScale, kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1,
kSegmentNumber1);
listener_->OnCueEvent(kStartTime2, "dummy cue data"); listener_->OnCueEvent(kStartTime2, "dummy cue data");
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2,
kSegmentNumber2);
::testing::Mock::VerifyAndClearExpectations(notifier_.get()); ::testing::Mock::VerifyAndClearExpectations(notifier_.get());
InSequence s; InSequence s;
EXPECT_CALL(*notifier_, NotifyNewContainer( EXPECT_CALL(*notifier_, NotifyNewContainer(
ExpectMediaInfoEq(kExpectedDefaultMediaInfoSubsegmentRange), _)) ExpectMediaInfoEq(kExpectedDefaultMediaInfoSubsegmentRange), _))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1,
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); kSegmentFileSize1, kSegmentNumber1));
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); kSegmentFileSize2, kSegmentNumber2));
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
} }
@ -485,15 +493,18 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) {
const uint64_t kSegmentFileSize1 = 29812u; const uint64_t kSegmentFileSize1 = 29812u;
const int64_t kStartTime2 = 1001; const int64_t kStartTime2 = 1001;
const int64_t kDuration2 = 3787; const int64_t kDuration2 = 3787;
const int64_t kSegmentNumber1 = 1;
const uint64_t kSegmentFileSize2 = 83743u; const uint64_t kSegmentFileSize2 = 83743u;
const int64_t kSegmentNumber2 = 2;
// Expectation for first file before OnMediaEnd. // Expectation for first file before OnMediaEnd.
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); 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, listener_->OnMediaStart(muxer_options1, *video_stream_info,
kDefaultReferenceTimeScale, kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1,
kSegmentNumber1);
listener_->OnCueEvent(kStartTime2, "dummy cue data"); listener_->OnCueEvent(kStartTime2, "dummy cue data");
::testing::Mock::VerifyAndClearExpectations(notifier_.get()); ::testing::Mock::VerifyAndClearExpectations(notifier_.get());
@ -502,8 +513,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) {
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_,
NotifyNewContainer(EqualsProto(expected_media_info1), _)) NotifyNewContainer(EqualsProto(expected_media_info1), _))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1,
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); kSegmentFileSize1, kSegmentNumber1));
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
@ -512,13 +523,14 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) {
listener_->OnMediaStart(muxer_options2, *video_stream_info, listener_->OnMediaStart(muxer_options2, *video_stream_info,
kDefaultReferenceTimeScale, kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2,
kSegmentNumber2);
// Expectation for second file OnMediaEnd. // Expectation for second file OnMediaEnd.
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_,
NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2)));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); kSegmentFileSize2, kSegmentNumber2));
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
} }
@ -544,17 +556,20 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) {
const int64_t kStartTime1 = 0; const int64_t kStartTime1 = 0;
const int64_t kDuration1 = 1000; const int64_t kDuration1 = 1000;
const uint64_t kSegmentFileSize1 = 29812u; const uint64_t kSegmentFileSize1 = 29812u;
const int64_t kSegmentNumber1 = 1;
const int64_t kStartTime2 = 1001; const int64_t kStartTime2 = 1001;
const int64_t kDuration2 = 3787; const int64_t kDuration2 = 3787;
const uint64_t kSegmentFileSize2 = 83743u; const uint64_t kSegmentFileSize2 = 83743u;
const int64_t kSegmentNumber2 = 2;
// Expectation for first file before OnMediaEnd. // Expectation for first file before OnMediaEnd.
EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); 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, listener_->OnMediaStart(muxer_options1, *video_stream_info,
kDefaultReferenceTimeScale, kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1,
kSegmentNumber1);
listener_->OnCueEvent(kStartTime2, "dummy cue data"); listener_->OnCueEvent(kStartTime2, "dummy cue data");
::testing::Mock::VerifyAndClearExpectations(notifier_.get()); ::testing::Mock::VerifyAndClearExpectations(notifier_.get());
@ -563,8 +578,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) {
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_,
NotifyNewContainer(EqualsProto(expected_media_info1), _)) NotifyNewContainer(EqualsProto(expected_media_info1), _))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1,
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); kSegmentFileSize1, kSegmentNumber1));
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
@ -573,13 +588,14 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) {
listener_->OnMediaStart(muxer_options2, *video_stream_info, listener_->OnMediaStart(muxer_options2, *video_stream_info,
kDefaultReferenceTimeScale, kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2,
kSegmentNumber2);
// Expectation for second file OnMediaEnd. // Expectation for second file OnMediaEnd.
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_,
NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2)));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); kSegmentFileSize2, kSegmentNumber2));
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams());
} }
@ -613,6 +629,8 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) {
const uint64_t kDuration = 1000u; const uint64_t kDuration = 1000u;
const uint64_t kSegmentSize1 = 29812u; const uint64_t kSegmentSize1 = 29812u;
const uint64_t kSegmentSize2 = 30128u; const uint64_t kSegmentSize2 = 30128u;
const int64_t kSegmentNumber1 = 1;
const int64_t kSegmentNumber2 = 2;
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_,
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
@ -622,11 +640,11 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) {
EXPECT_CALL(*notifier_, NotifyAvailabilityTimeOffset(_)) EXPECT_CALL(*notifier_, NotifyAvailabilityTimeOffset(_))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(*notifier_, NotifySegmentDuration(_)).WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifySegmentDuration(_)).WillOnce(Return(true));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration,
NotifyNewSegment(_, kStartTime1, kDuration, kSegmentSize1)); kSegmentSize1, kSegmentNumber1));
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration,
NotifyNewSegment(_, kStartTime2, kDuration, kSegmentSize2)); kSegmentSize2, kSegmentNumber2));
EXPECT_CALL(*notifier_, Flush()).Times(2); EXPECT_CALL(*notifier_, Flush()).Times(2);
listener_->OnMediaStart(muxer_options, *video_stream_info, listener_->OnMediaStart(muxer_options, *video_stream_info,
@ -635,9 +653,11 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) {
listener_->OnSampleDurationReady(kDuration); listener_->OnSampleDurationReady(kDuration);
listener_->OnAvailabilityOffsetReady(); listener_->OnAvailabilityOffsetReady();
listener_->OnSegmentDurationReady(); listener_->OnSegmentDurationReady();
listener_->OnNewSegment("", kStartTime1, kDuration, kSegmentSize1); listener_->OnNewSegment("", kStartTime1, kDuration, kSegmentSize1,
kSegmentNumber1);
listener_->OnCueEvent(kStartTime2, "dummy cue data"); listener_->OnCueEvent(kStartTime2, "dummy cue data");
listener_->OnNewSegment("", kStartTime2, kDuration, kSegmentSize2); listener_->OnNewSegment("", kStartTime2, kDuration, kSegmentSize2,
kSegmentNumber2);
::testing::Mock::VerifyAndClearExpectations(notifier_.get()); ::testing::Mock::VerifyAndClearExpectations(notifier_.get());
EXPECT_CALL(*notifier_, Flush()).Times(0); EXPECT_CALL(*notifier_, Flush()).Times(0);
@ -686,7 +706,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) {
const uint64_t kSegmentFileSize1 = 29812u; const uint64_t kSegmentFileSize1 = 29812u;
const int64_t kStartTime2 = 1001; const int64_t kStartTime2 = 1001;
const int64_t kDuration2 = 3787; const int64_t kDuration2 = 3787;
const int64_t kSegmentNumber1 = 1;
const uint64_t kSegmentFileSize2 = 83743u; const uint64_t kSegmentFileSize2 = 83743u;
const int64_t kSegmentNumber2 = 2;
const std::vector<uint8_t> default_key_id( const std::vector<uint8_t> default_key_id(
kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1); kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1);
@ -695,14 +718,14 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) {
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_,
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1,
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); kSegmentFileSize1, kSegmentNumber1));
// Flush should only be called once in OnMediaEnd. // Flush should only be called once in OnMediaEnd.
if (GetParam() == MpdType::kDynamic) if (GetParam() == MpdType::kDynamic)
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2));
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); kSegmentFileSize2, kSegmentNumber2));
if (GetParam() == MpdType::kDynamic) if (GetParam() == MpdType::kDynamic)
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
@ -713,9 +736,11 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) {
listener_->OnMediaStart(muxer_options, *video_stream_info, listener_->OnMediaStart(muxer_options, *video_stream_info,
kDefaultReferenceTimeScale, kDefaultReferenceTimeScale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1,
kSegmentNumber1);
listener_->OnCueEvent(kStartTime2, "dummy cue data"); listener_->OnCueEvent(kStartTime2, "dummy cue data");
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2,
kSegmentNumber2);
::testing::Mock::VerifyAndClearExpectations(notifier_.get()); ::testing::Mock::VerifyAndClearExpectations(notifier_.get());
EXPECT_CALL(*notifier_, Flush()) EXPECT_CALL(*notifier_, Flush())
@ -760,7 +785,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) {
const uint64_t kSegmentFileSize1 = 29812u; const uint64_t kSegmentFileSize1 = 29812u;
const int64_t kStartTime2 = 1001; const int64_t kStartTime2 = 1001;
const int64_t kDuration2 = 3787; const int64_t kDuration2 = 3787;
const int64_t kSegmentNumber1 = 1;
const uint64_t kSegmentFileSize2 = 83743u; const uint64_t kSegmentFileSize2 = 83743u;
const int64_t kSegmentNumber2 = 2;
const std::vector<uint8_t> default_key_id( const std::vector<uint8_t> default_key_id(
kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1); kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1);
@ -769,13 +797,13 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) {
NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1); EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1);
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1,
NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); kSegmentFileSize1, kSegmentNumber1));
// Flush should only be called once in OnMediaEnd. // Flush should only be called once in OnMediaEnd.
if (GetParam() == MpdType::kDynamic) if (GetParam() == MpdType::kDynamic)
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
EXPECT_CALL(*notifier_, EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2,
NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); kSegmentFileSize2, kSegmentNumber2));
if (GetParam() == MpdType::kDynamic) if (GetParam() == MpdType::kDynamic)
EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, Flush());
@ -789,8 +817,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) {
listener_->OnEncryptionInfoReady(kNonInitialEncryptionInfo, FOURCC_cbc1, listener_->OnEncryptionInfoReady(kNonInitialEncryptionInfo, FOURCC_cbc1,
std::vector<uint8_t>(), iv, std::vector<uint8_t>(), iv,
GetDefaultKeySystemInfo()); GetDefaultKeySystemInfo());
listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1,
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); kSegmentNumber1);
listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2,
kSegmentNumber2);
::testing::Mock::VerifyAndClearExpectations(notifier_.get()); ::testing::Mock::VerifyAndClearExpectations(notifier_.get());
EXPECT_CALL(*notifier_, Flush()) EXPECT_CALL(*notifier_, Flush())

View File

@ -27,6 +27,8 @@ const int64_t kSegmentStartTime = 19283;
const int64_t kSegmentDuration = 98028; const int64_t kSegmentDuration = 98028;
const uint64_t kSegmentSize = 756739; const uint64_t kSegmentSize = 756739;
const int32_t kTimescale = 90000; const int32_t kTimescale = 90000;
const int64_t kSegmentNumber = 10;
MuxerListener::ContainerType kContainer = MuxerListener::kContainerMpeg2ts; MuxerListener::ContainerType kContainer = MuxerListener::kContainerMpeg2ts;
} // namespace } // namespace
@ -79,10 +81,11 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartSingleCodec) {
EXPECT_CALL(*listener_for_first_codec_, EXPECT_CALL(*listener_for_first_codec_,
OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime,
kSegmentDuration, kSegmentSize)); kSegmentDuration, kSegmentSize, kSegmentNumber));
multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime,
kSegmentDuration, kSegmentSize); kSegmentDuration, kSegmentSize,
kSegmentNumber);
} }
TEST_F(MultiCodecMuxerListenerTest, OnMediaStartTwoCodecs) { TEST_F(MultiCodecMuxerListenerTest, OnMediaStartTwoCodecs) {
@ -114,13 +117,14 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartTwoCodecs) {
EXPECT_CALL(*listener_for_first_codec_, EXPECT_CALL(*listener_for_first_codec_,
OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime,
kSegmentDuration, kSegmentSize)); kSegmentDuration, kSegmentSize, kSegmentNumber));
EXPECT_CALL(*listener_for_second_codec_, EXPECT_CALL(*listener_for_second_codec_,
OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime,
kSegmentDuration, kSegmentSize)); kSegmentDuration, kSegmentSize, kSegmentNumber));
multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime,
kSegmentDuration, kSegmentSize); kSegmentDuration, kSegmentSize,
kSegmentNumber);
} }
} // namespace media } // namespace media

View File

@ -133,10 +133,12 @@ class MuxerListener {
/// @param duration is the duration of the segment, relative to the timescale /// @param duration is the duration of the segment, relative to the timescale
/// specified by MediaInfo passed to OnMediaStart(). /// specified by MediaInfo passed to OnMediaStart().
/// @param segment_file_size is the segment size in bytes. /// @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, virtual void OnNewSegment(const std::string& segment_name,
int64_t start_time, int64_t start_time,
int64_t duration, 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. /// 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. /// For Low Latency only. Note that it should be called after OnNewSegment.

View File

@ -95,7 +95,8 @@ void VodMediaInfoDumpMuxerListener::OnMediaEnd(const MediaRanges& media_ranges,
void VodMediaInfoDumpMuxerListener::OnNewSegment(const std::string& file_name, void VodMediaInfoDumpMuxerListener::OnNewSegment(const std::string& file_name,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t segment_file_size) { uint64_t segment_file_size,
int64_t segment_number) {
UNUSED(file_name); UNUSED(file_name);
UNUSED(start_time); UNUSED(start_time);
const double segment_duration_seconds = const double segment_duration_seconds =

View File

@ -49,7 +49,8 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
void OnNewSegment(const std::string& file_name, void OnNewSegment(const std::string& file_name,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t segment_file_size) override; uint64_t segment_file_size,
int64_t segment_number) override;
void OnKeyFrame(int64_t timestamp, void OnKeyFrame(int64_t timestamp,
uint64_t start_byte_offset, uint64_t start_byte_offset,
uint64_t size) override; uint64_t size) override;

View File

@ -102,8 +102,10 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test {
} }
void FireOnNewSegmentWithParams(const OnNewSegmentParameters& params) { void FireOnNewSegmentWithParams(const OnNewSegmentParameters& params) {
const int64_t kSegmentNumber = 1;
listener_->OnNewSegment(params.file_name, params.start_time, 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) { void FireOnMediaEndWithParams(const OnMediaEndParameters& params) {

View File

@ -82,7 +82,7 @@ Status TsMuxer::FinalizeSegment(size_t stream_id,
options().segment_template.empty() options().segment_template.empty()
? options().output_file_name ? options().output_file_name
: GetSegmentName(options().segment_template, segment_start_timestamp, : 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(); const int64_t file_size = segmenter_->segment_buffer()->Size();
@ -95,7 +95,8 @@ Status TsMuxer::FinalizeSegment(size_t stream_id,
segment_path, segment_path,
segment_info.start_timestamp * segmenter_->timescale() + segment_info.start_timestamp * segmenter_->timescale() +
segmenter_->transport_stream_timestamp_offset(), 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); segmenter_->set_segment_started(false);

View File

@ -41,9 +41,6 @@ class TsMuxer : public Muxer {
int64_t sample_durations_[2] = {0, 0}; int64_t sample_durations_[2] = {0, 0};
size_t num_samples_ = 0; size_t num_samples_ = 0;
// Used in multi-segment mode for segment template.
uint64_t segment_number_ = 0;
// Used in single segment mode. // Used in single segment mode.
std::unique_ptr<File, FileCloser> output_file_; std::unique_ptr<File, FileCloser> output_file_;

View File

@ -54,6 +54,7 @@ class TsSegmenter {
/// stream's time scale. /// stream's time scale.
/// @param duration is the segment's duration in the input stream's time /// @param duration is the segment's duration in the input stream's time
/// scale. /// scale.
/// @param segment_number is the segment number.
// TODO(kqyang): Remove the usage of segment start timestamp and duration in // 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 // xx_segmenter, which could cause confusions on which is the source of truth
// as the segment start timestamp and duration could be tracked locally. // as the segment start timestamp and duration could be tracked locally.

View File

@ -84,6 +84,7 @@ class MockTsWriter : public TsWriter {
MOCK_METHOD1(NewSegment, bool(BufferWriter* buffer_writer)); MOCK_METHOD1(NewSegment, bool(BufferWriter* buffer_writer));
MOCK_METHOD0(SignalEncrypted, void()); MOCK_METHOD0(SignalEncrypted, void());
MOCK_METHOD0(FinalizeSegment, bool());
// Similar to the hack above but takes a std::unique_ptr. // Similar to the hack above but takes a std::unique_ptr.
MOCK_METHOD2(AddPesPacketMock, bool(PesPacket* pes_packet, MOCK_METHOD2(AddPesPacketMock, bool(PesPacket* pes_packet,
@ -232,7 +233,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
.InSequence(ready_pes_sequence) .InSequence(ready_pes_sequence)
.WillOnce(Return(1u)); .WillOnce(Return(1u));
EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
.InSequence(ready_pes_sequence) .InSequence(ready_pes_sequence)
.WillOnce(Return(0u)); .WillOnce(Return(0u));

View File

@ -188,8 +188,7 @@ void TsWriter::SignalEncrypted() {
} }
bool TsWriter::AddPesPacket(std::unique_ptr<PesPacket> pes_packet, bool TsWriter::AddPesPacket(std::unique_ptr<PesPacket> pes_packet,
BufferWriter* buffer) { BufferWriter* buffer) {
if (!WritePesToBuffer(*pes_packet, &elementary_stream_continuity_counter_, if (!WritePesToBuffer(*pes_packet, &elementary_stream_continuity_counter_,
buffer)) { buffer)) {
LOG(ERROR) << "Failed to write pes to buffer."; LOG(ERROR) << "Failed to write pes to buffer.";

View File

@ -71,13 +71,13 @@ Status LowLatencySegmentSegmenter::DoFinalize() {
return Status::OK; return Status::OK;
} }
Status LowLatencySegmentSegmenter::DoFinalizeSegment() { Status LowLatencySegmentSegmenter::DoFinalizeSegment(int64_t segment_number) {
return FinalizeSegment(); return FinalizeSegment();
} }
Status LowLatencySegmentSegmenter::DoFinalizeChunk() { Status LowLatencySegmentSegmenter::DoFinalizeChunk(int64_t segment_number) {
if (is_initial_chunk_in_seg_) { if (is_initial_chunk_in_seg_) {
return WriteInitialChunk(); return WriteInitialChunk(segment_number);
} }
return WriteChunk(); return WriteChunk();
} }
@ -98,7 +98,7 @@ Status LowLatencySegmentSegmenter::WriteInitSegment() {
return buffer->WriteToFile(file.get()); return buffer->WriteToFile(file.get());
} }
Status LowLatencySegmentSegmenter::WriteInitialChunk() { Status LowLatencySegmentSegmenter::WriteInitialChunk(int64_t segment_number) {
DCHECK(sidx()); DCHECK(sidx());
DCHECK(fragment_buffer()); DCHECK(fragment_buffer());
DCHECK(styp_); DCHECK(styp_);
@ -161,9 +161,9 @@ Status LowLatencySegmentSegmenter::WriteInitialChunk() {
} }
// Add the current segment in the manifest. // Add the current segment in the manifest.
// Following chunks will be appended to the open segment file. // Following chunks will be appended to the open segment file.
muxer_listener()->OnNewSegment(file_name_, muxer_listener()->OnNewSegment(
sidx()->earliest_presentation_time, file_name_, sidx()->earliest_presentation_time, segment_duration,
segment_duration, segment_size_); segment_size_, segment_number);
is_initial_chunk_in_seg_ = false; is_initial_chunk_in_seg_ = false;
} }

View File

@ -44,13 +44,13 @@ class LowLatencySegmentSegmenter : public Segmenter {
// Segmenter implementation overrides. // Segmenter implementation overrides.
Status DoInitialize() override; Status DoInitialize() override;
Status DoFinalize() override; Status DoFinalize() override;
Status DoFinalizeSegment() override; Status DoFinalizeSegment(int64_t segment_number) override;
Status DoFinalizeChunk() override; Status DoFinalizeChunk(int64_t segment_number) override;
// Write segment to file. // Write segment to file.
Status WriteInitSegment(); Status WriteInitSegment();
Status WriteChunk(); Status WriteChunk();
Status WriteInitialChunk(); Status WriteInitialChunk(int64_t segment_number);
Status FinalizeSegment(); Status FinalizeSegment();
uint64_t GetSegmentDuration(); uint64_t GetSegmentDuration();

View File

@ -209,7 +209,8 @@ Status MP4Muxer::FinalizeSegment(size_t stream_id,
DCHECK(segmenter_); DCHECK(segmenter_);
VLOG(3) << "Finalizing " << (segment_info.is_subsegment ? "sub" : "") VLOG(3) << "Finalizing " << (segment_info.is_subsegment ? "sub" : "")
<< "segment " << segment_info.start_timestamp << " duration " << "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); return segmenter_->FinalizeSegment(stream_id, segment_info);
} }

View File

@ -31,8 +31,7 @@ MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options,
std::unique_ptr<FileType> ftyp, std::unique_ptr<FileType> ftyp,
std::unique_ptr<Movie> moov) std::unique_ptr<Movie> moov)
: Segmenter(options, std::move(ftyp), std::move(moov)), : Segmenter(options, std::move(ftyp), std::move(moov)),
styp_(new SegmentType), styp_(new SegmentType) {
num_segments_(0) {
// Use the same brands for styp as ftyp. // Use the same brands for styp as ftyp.
styp_->major_brand = Segmenter::ftyp()->major_brand; styp_->major_brand = Segmenter::ftyp()->major_brand;
styp_->compatible_brands = Segmenter::ftyp()->compatible_brands; styp_->compatible_brands = Segmenter::ftyp()->compatible_brands;
@ -70,8 +69,8 @@ Status MultiSegmentSegmenter::DoFinalize() {
return Status::OK; return Status::OK;
} }
Status MultiSegmentSegmenter::DoFinalizeSegment() { Status MultiSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) {
return WriteSegment(); return WriteSegment(segment_number);
} }
Status MultiSegmentSegmenter::WriteInitSegment() { Status MultiSegmentSegmenter::WriteInitSegment() {
@ -90,7 +89,7 @@ Status MultiSegmentSegmenter::WriteInitSegment() {
return buffer->WriteToFile(file.get()); return buffer->WriteToFile(file.get());
} }
Status MultiSegmentSegmenter::WriteSegment() { Status MultiSegmentSegmenter::WriteSegment(int64_t segment_number) {
DCHECK(sidx()); DCHECK(sidx());
DCHECK(fragment_buffer()); DCHECK(fragment_buffer());
DCHECK(styp_); DCHECK(styp_);
@ -115,7 +114,7 @@ Status MultiSegmentSegmenter::WriteSegment() {
} else { } else {
file_name = GetSegmentName(options().segment_template, file_name = GetSegmentName(options().segment_template,
sidx()->earliest_presentation_time, sidx()->earliest_presentation_time,
num_segments_++, options().bandwidth); segment_number, options().bandwidth);
file.reset(File::Open(file_name.c_str(), "w")); file.reset(File::Open(file_name.c_str(), "w"));
if (!file) { if (!file) {
return Status(error::FILE_FAILURE, return Status(error::FILE_FAILURE,
@ -160,9 +159,9 @@ Status MultiSegmentSegmenter::WriteSegment() {
UpdateProgress(segment_duration); UpdateProgress(segment_duration);
if (muxer_listener()) { if (muxer_listener()) {
muxer_listener()->OnSampleDurationReady(sample_duration()); muxer_listener()->OnSampleDurationReady(sample_duration());
muxer_listener()->OnNewSegment(file_name, muxer_listener()->OnNewSegment(
sidx()->earliest_presentation_time, file_name, sidx()->earliest_presentation_time, segment_duration,
segment_duration, segment_size); segment_size, segment_number);
} }
return Status::OK; return Status::OK;

View File

@ -39,14 +39,13 @@ class MultiSegmentSegmenter : public Segmenter {
// Segmenter implementation overrides. // Segmenter implementation overrides.
Status DoInitialize() override; Status DoInitialize() override;
Status DoFinalize() override; Status DoFinalize() override;
Status DoFinalizeSegment() override; Status DoFinalizeSegment(int64_t segment_number) override;
// Write segment to file. // Write segment to file.
Status WriteInitSegment(); Status WriteInitSegment();
Status WriteSegment(); Status WriteSegment(int64_t segment_number);
std::unique_ptr<SegmentType> styp_; std::unique_ptr<SegmentType> styp_;
uint32_t num_segments_;
DISALLOW_COPY_AND_ASSIGN(MultiSegmentSegmenter); DISALLOW_COPY_AND_ASSIGN(MultiSegmentSegmenter);
}; };

View File

@ -234,14 +234,15 @@ Status Segmenter::FinalizeSegment(size_t stream_id,
if (segment_info.is_chunk) { if (segment_info.is_chunk) {
// Finalize the completed chunk for the LL-DASH case. // Finalize the completed chunk for the LL-DASH case.
status = DoFinalizeChunk(); status = DoFinalizeChunk(segment_info.segment_number);
if (!status.ok()) if (!status.ok())
return status; return status;
} }
if (!segment_info.is_subsegment || segment_info.is_final_chunk_in_seg) { if (!segment_info.is_subsegment || segment_info.is_final_chunk_in_seg) {
// Finalize the segment. // Finalize the segment.
status = DoFinalizeSegment(); status = DoFinalizeSegment(segment_info.segment_number);
// Reset segment information to initial state. // Reset segment information to initial state.
sidx_->references.clear(); sidx_->references.clear();
key_frame_infos_.clear(); key_frame_infos_.clear();

View File

@ -127,9 +127,8 @@ class Segmenter {
private: private:
virtual Status DoInitialize() = 0; virtual Status DoInitialize() = 0;
virtual Status DoFinalize() = 0; virtual Status DoFinalize() = 0;
virtual Status DoFinalizeSegment() = 0; virtual Status DoFinalizeSegment(int64_t segment_number) = 0;
virtual Status DoFinalizeChunk(int64_t segment_number) { return Status::OK; }
virtual Status DoFinalizeChunk() { return Status::OK; }
uint32_t GetReferenceStreamId(); uint32_t GetReferenceStreamId();

View File

@ -166,7 +166,7 @@ Status SingleSegmentSegmenter::DoFinalize() {
return Status::OK; return Status::OK;
} }
Status SingleSegmentSegmenter::DoFinalizeSegment() { Status SingleSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) {
DCHECK(sidx()); DCHECK(sidx());
DCHECK(fragment_buffer()); DCHECK(fragment_buffer());
// sidx() contains pre-generated segment references with one reference per // sidx() contains pre-generated segment references with one reference per
@ -224,9 +224,9 @@ Status SingleSegmentSegmenter::DoFinalizeSegment() {
UpdateProgress(vod_ref.subsegment_duration); UpdateProgress(vod_ref.subsegment_duration);
if (muxer_listener()) { if (muxer_listener()) {
muxer_listener()->OnSampleDurationReady(sample_duration()); muxer_listener()->OnSampleDurationReady(sample_duration());
muxer_listener()->OnNewSegment(options().output_file_name, muxer_listener()->OnNewSegment(
vod_ref.earliest_presentation_time, options().output_file_name, vod_ref.earliest_presentation_time,
vod_ref.subsegment_duration, segment_size); vod_ref.subsegment_duration, segment_size, segment_number);
} }
return Status::OK; return Status::OK;
} }

View File

@ -44,7 +44,7 @@ class SingleSegmentSegmenter : public Segmenter {
// Segmenter implementation overrides. // Segmenter implementation overrides.
Status DoInitialize() override; Status DoInitialize() override;
Status DoFinalize() override; Status DoFinalize() override;
Status DoFinalizeSegment() override; Status DoFinalizeSegment(int64_t segment_number) override;
std::unique_ptr<SegmentIndex> vod_sidx_; std::unique_ptr<SegmentIndex> vod_sidx_;
std::string temp_file_name_; std::string temp_file_name_;

View File

@ -81,7 +81,7 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id,
options().segment_template.empty() options().segment_template.empty()
? options().output_file_name ? options().output_file_name
: GetSegmentName(options().segment_template, segment_timestamp, : 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. // Save |segment_size| as it will be cleared after writing.
const size_t segment_size = segmenter_->segment_buffer()->Size(); const size_t segment_size = segmenter_->segment_buffer()->Size();
@ -92,7 +92,8 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id,
if (muxer_listener()) { if (muxer_listener()) {
muxer_listener()->OnNewSegment( muxer_listener()->OnNewSegment(
segment_path, segment_timestamp + transport_stream_timestamp_offset_, 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; return Status::OK;
} }

View File

@ -44,6 +44,8 @@ const char kOutputFile[] = "memory://test.aac";
const char kSegmentTemplate[] = "memory://test_$Number$.aac"; const char kSegmentTemplate[] = "memory://test_$Number$.aac";
const char kSegment1Name[] = "memory://test_1.aac"; const char kSegment1Name[] = "memory://test_1.aac";
const char kSegment2Name[] = "memory://test_2.aac"; const char kSegment2Name[] = "memory://test_2.aac";
const int64_t kSegmentNumber1 = 1;
const int64_t kSegmentNumber2 = 2;
class MockPackedAudioSegmenter : public PackedAudioSegmenter { class MockPackedAudioSegmenter : public PackedAudioSegmenter {
public: public:
@ -122,9 +124,10 @@ TEST_P(PackedAudioWriterTest, SubsegmentIgnored) {
const int64_t kDuration = 100; const int64_t kDuration = 100;
const bool kSubsegment = true; const bool kSubsegment = true;
auto subsegment_stream_data = StreamData::FromSegmentInfo( 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); EXPECT_CALL(*mock_segmenter_ptr_, FinalizeSegment()).Times(0);
ASSERT_OK(Input(kInput)->Dispatch(std::move(subsegment_stream_data))); ASSERT_OK(Input(kInput)->Dispatch(std::move(subsegment_stream_data)));
} }
@ -137,7 +140,8 @@ TEST_P(PackedAudioWriterTest, OneSegment) {
const int64_t kDuration = 100; const int64_t kDuration = 100;
const bool kSubsegment = true; const bool kSubsegment = true;
auto segment_stream_data = StreamData::FromSegmentInfo( auto segment_stream_data = StreamData::FromSegmentInfo(
kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, !kSubsegment)); kStreamIndex,
GetSegmentInfo(kTimestamp, kDuration, !kSubsegment, kSegmentNumber1));
const double kMockTimescaleScale = 10; const double kMockTimescaleScale = 10;
const char kMockSegmentData[] = "hello segment 1"; const char kMockSegmentData[] = "hello segment 1";
@ -147,7 +151,8 @@ TEST_P(PackedAudioWriterTest, OneSegment) {
*mock_muxer_listener_ptr_, *mock_muxer_listener_ptr_,
OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name,
kTimestamp * kMockTimescaleScale, kTimestamp * kMockTimescaleScale,
kDuration * kMockTimescaleScale, kSegmentDataSize)); kDuration * kMockTimescaleScale, kSegmentDataSize,
kSegmentNumber1));
EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale())
.WillRepeatedly(Return(kMockTimescaleScale)); .WillRepeatedly(Return(kMockTimescaleScale));
@ -190,10 +195,11 @@ TEST_P(PackedAudioWriterTest, TwoSegments) {
const int64_t kDuration = 100; const int64_t kDuration = 100;
const bool kSubsegment = true; const bool kSubsegment = true;
auto segment1_stream_data = StreamData::FromSegmentInfo( auto segment1_stream_data = StreamData::FromSegmentInfo(
kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, !kSubsegment));
auto segment2_stream_data = StreamData::FromSegmentInfo(
kStreamIndex, 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 double kMockTimescaleScale = 10;
const char kMockSegment1Data[] = "hello segment 1"; const char kMockSegment1Data[] = "hello segment 1";
@ -206,12 +212,13 @@ TEST_P(PackedAudioWriterTest, TwoSegments) {
OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name,
kTimestamp * kMockTimescaleScale, kTimestamp * kMockTimescaleScale,
kDuration * kMockTimescaleScale, kDuration * kMockTimescaleScale,
sizeof(kMockSegment1Data) - 1)); sizeof(kMockSegment1Data) - 1, kSegmentNumber1));
EXPECT_CALL( EXPECT_CALL(
*mock_muxer_listener_ptr_, *mock_muxer_listener_ptr_,
OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment2Name, OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment2Name,
(kTimestamp + kDuration) * kMockTimescaleScale, (kTimestamp + kDuration) * kMockTimescaleScale,
kDuration * kMockTimescaleScale, kSegment2DataSize)); kDuration * kMockTimescaleScale, kSegment2DataSize,
kSegmentNumber2));
EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale())
.WillRepeatedly(Return(kMockTimescaleScale)); .WillRepeatedly(Return(kMockTimescaleScale));

View File

@ -18,6 +18,9 @@ const int32_t kTimeScale = 1000000;
const int64_t kDuration = 1000000; const int64_t kDuration = 1000000;
const bool kSubsegment = true; const bool kSubsegment = true;
const uint8_t kPerSampleIvSize = 8u; const uint8_t kPerSampleIvSize = 8u;
const int64_t kSegmentNumber1 = 1;
const int64_t kSegmentNumber2 = 2;
const uint8_t kKeyId[] = { const uint8_t kKeyId[] = {
0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70,
0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f,
@ -229,7 +232,8 @@ TEST_F(EncryptedSegmenterTest, BasicSupport) {
// segment encrypted. // segment encrypted.
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
if (i == 3) { if (i == 3) {
ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment,
kSegmentNumber1));
} }
std::shared_ptr<MediaSample> sample = std::shared_ptr<MediaSample> sample =
CreateSample(kKeyFrame, kDuration, kNoSideData); CreateSample(kKeyFrame, kDuration, kNoSideData);
@ -243,8 +247,8 @@ TEST_F(EncryptedSegmenterTest, BasicSupport) {
} }
ASSERT_OK(segmenter_->AddSample(*sample)); ASSERT_OK(segmenter_->AddSample(*sample));
} }
ASSERT_OK( ASSERT_OK(segmenter_->FinalizeSegment(3 * kDuration, 2 * kDuration,
segmenter_->FinalizeSegment(3 * kDuration, 2 * kDuration, !kSubsegment)); !kSubsegment, kSegmentNumber2));
ASSERT_OK(segmenter_->Finalize()); ASSERT_OK(segmenter_->Finalize());
ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData); ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData);

View File

@ -27,17 +27,18 @@ MultiSegmentSegmenter::~MultiSegmentSegmenter() {}
Status MultiSegmentSegmenter::FinalizeSegment(int64_t start_timestamp, Status MultiSegmentSegmenter::FinalizeSegment(int64_t start_timestamp,
int64_t duration_timestamp, int64_t duration_timestamp,
bool is_subsegment) { bool is_subsegment,
int64_t segment_number) {
CHECK(cluster()); CHECK(cluster());
RETURN_IF_ERROR(Segmenter::FinalizeSegment( RETURN_IF_ERROR(Segmenter::FinalizeSegment(
start_timestamp, duration_timestamp, is_subsegment)); start_timestamp, duration_timestamp, is_subsegment, segment_number));
if (!cluster()->Finalize()) if (!cluster()->Finalize())
return Status(error::FILE_FAILURE, "Error finalizing segment."); return Status(error::FILE_FAILURE, "Error finalizing segment.");
if (!is_subsegment) { if (!is_subsegment) {
std::string segment_name = std::string segment_name =
GetSegmentName(options().segment_template, start_timestamp, 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 // Close the file, which also does flushing, to make sure the file is
// written before manifest is updated. // written before manifest is updated.
@ -54,7 +55,7 @@ Status MultiSegmentSegmenter::FinalizeSegment(int64_t start_timestamp,
if (muxer_listener()) { if (muxer_listener()) {
const uint64_t size = cluster()->Size(); const uint64_t size = cluster()->Size();
muxer_listener()->OnNewSegment(segment_name, start_timestamp, muxer_listener()->OnNewSegment(segment_name, start_timestamp,
duration_timestamp, size); duration_timestamp, size, segment_number);
} }
VLOG(1) << "WEBM file '" << segment_name << "' finalized."; VLOG(1) << "WEBM file '" << segment_name << "' finalized.";
} }

View File

@ -32,7 +32,8 @@ class MultiSegmentSegmenter : public Segmenter {
/// @{ /// @{
Status FinalizeSegment(int64_t start_timestamp, Status FinalizeSegment(int64_t start_timestamp,
int64_t duration_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 GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
std::vector<Range> GetSegmentRanges() override; std::vector<Range> GetSegmentRanges() override;

View File

@ -17,6 +17,8 @@ namespace {
const int32_t kTimeScale = 1000000; const int32_t kTimeScale = 1000000;
const int64_t kDuration = 1000000; const int64_t kDuration = 1000000;
const int64_t kSegmentNumber1 = 1;
const int64_t kSegmentNumber2 = 2;
const bool kSubsegment = true; const bool kSubsegment = true;
// clang-format off // clang-format off
@ -130,15 +132,16 @@ TEST_F(MultiSegmentSegmenterTest, BasicSupport) {
CreateSample(kKeyFrame, kDuration, kNoSideData); CreateSample(kKeyFrame, kDuration, kNoSideData);
ASSERT_OK(segmenter_->AddSample(*sample)); 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()); ASSERT_OK(segmenter_->Finalize());
// Verify the resulting data. // Verify the resulting data.
ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportDataInit); 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. // 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) { TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) {
@ -149,27 +152,28 @@ TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) {
// Write the samples to the Segmenter. // Write the samples to the Segmenter.
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
if (i == 5) { if (i == 5) {
ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment,
kSegmentNumber1));
} }
std::shared_ptr<MediaSample> sample = std::shared_ptr<MediaSample> sample =
CreateSample(kKeyFrame, kDuration, kNoSideData); CreateSample(kKeyFrame, kDuration, kNoSideData);
ASSERT_OK(segmenter_->AddSample(*sample)); ASSERT_OK(segmenter_->AddSample(*sample));
} }
ASSERT_OK( ASSERT_OK(segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration,
segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment)); !kSubsegment, kSegmentNumber2));
ASSERT_OK(segmenter_->Finalize()); ASSERT_OK(segmenter_->Finalize());
// Verify the resulting data. // Verify the resulting data.
ClusterParser parser; ClusterParser parser;
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1)));
ASSERT_EQ(1u, parser.cluster_count()); ASSERT_EQ(1u, parser.cluster_count());
EXPECT_EQ(5u, parser.GetFrameCountForCluster(0)); 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()); ASSERT_EQ(1u, parser.cluster_count());
EXPECT_EQ(3u, parser.GetFrameCountForCluster(0)); 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) { TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) {
@ -180,23 +184,25 @@ TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) {
// Write the samples to the Segmenter. // Write the samples to the Segmenter.
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
if (i == 5) { if (i == 5) {
ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment)); ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment,
kSegmentNumber1));
} }
std::shared_ptr<MediaSample> sample = std::shared_ptr<MediaSample> sample =
CreateSample(kKeyFrame, kDuration, kNoSideData); CreateSample(kKeyFrame, kDuration, kNoSideData);
ASSERT_OK(segmenter_->AddSample(*sample)); 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()); ASSERT_OK(segmenter_->Finalize());
// Verify the resulting data. // Verify the resulting data.
ClusterParser parser; ClusterParser parser;
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1)));
ASSERT_EQ(2u, parser.cluster_count()); ASSERT_EQ(2u, parser.cluster_count());
EXPECT_EQ(5u, parser.GetFrameCountForCluster(0)); EXPECT_EQ(5u, parser.GetFrameCountForCluster(0));
EXPECT_EQ(3u, parser.GetFrameCountForCluster(1)); 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 } // namespace media

View File

@ -198,7 +198,8 @@ Status Segmenter::AddSample(const MediaSample& source_sample) {
Status Segmenter::FinalizeSegment(int64_t /*start_timestamp*/, Status Segmenter::FinalizeSegment(int64_t /*start_timestamp*/,
int64_t /*duration_timestamp*/, int64_t /*duration_timestamp*/,
bool is_subsegment) { bool is_subsegment,
int64_t segment_number) {
if (is_subsegment) if (is_subsegment)
new_subsegment_ = true; new_subsegment_ = true;
else else

View File

@ -58,7 +58,8 @@ class Segmenter {
/// Finalize the (sub)segment. /// Finalize the (sub)segment.
virtual Status FinalizeSegment(int64_t start_timestamp, virtual Status FinalizeSegment(int64_t start_timestamp,
int64_t duration_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 /// @return true if there is an initialization range, while setting @a start
/// and @a end; or false if initialization range does not apply. /// and @a end; or false if initialization range does not apply.

View File

@ -23,9 +23,10 @@ SingleSegmentSegmenter::~SingleSegmentSegmenter() {}
Status SingleSegmentSegmenter::FinalizeSegment(int64_t start_timestamp, Status SingleSegmentSegmenter::FinalizeSegment(int64_t start_timestamp,
int64_t duration_timestamp, int64_t duration_timestamp,
bool is_subsegment) { bool is_subsegment,
Status status = Segmenter::FinalizeSegment(start_timestamp, int64_t segment_number) {
duration_timestamp, is_subsegment); Status status = Segmenter::FinalizeSegment(
start_timestamp, duration_timestamp, is_subsegment, segment_number);
if (!status.ok()) if (!status.ok())
return status; return status;
// No-op for subsegment in single segment mode. // No-op for subsegment in single segment mode.
@ -37,7 +38,7 @@ Status SingleSegmentSegmenter::FinalizeSegment(int64_t start_timestamp,
if (muxer_listener()) { if (muxer_listener()) {
const uint64_t size = cluster()->Size(); const uint64_t size = cluster()->Size();
muxer_listener()->OnNewSegment(options().output_file_name, start_timestamp, muxer_listener()->OnNewSegment(options().output_file_name, start_timestamp,
duration_timestamp, size); duration_timestamp, size, segment_number);
} }
return Status::OK; return Status::OK;
} }

View File

@ -33,7 +33,8 @@ class SingleSegmentSegmenter : public Segmenter {
/// @{ /// @{
Status FinalizeSegment(int64_t start_timestamp, Status FinalizeSegment(int64_t start_timestamp,
int64_t duration_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 GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
std::vector<Range> GetSegmentRanges() override; std::vector<Range> GetSegmentRanges() override;

View File

@ -18,6 +18,8 @@ const int32_t kTimeScale = 1000000;
const int32_t kTimecodeScale = 1000000; const int32_t kTimecodeScale = 1000000;
const int64_t kSecondsToNs = 1000000000L; const int64_t kSecondsToNs = 1000000000L;
const int64_t kDuration = 1000000; const int64_t kDuration = 1000000;
const int64_t kSegmentNumber1 = 1;
const int64_t kSegmentNumber2 = 2;
const bool kSubsegment = true; const bool kSubsegment = true;
// clang-format off // clang-format off
@ -167,7 +169,8 @@ TEST_F(SingleSegmentSegmenterTest, BasicSupport) {
CreateSample(kKeyFrame, kDuration, side_data_flag); CreateSample(kKeyFrame, kDuration, side_data_flag);
ASSERT_OK(segmenter_->AddSample(*sample)); 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_OK(segmenter_->Finalize());
ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData); ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData);
@ -180,14 +183,15 @@ TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegment) {
// Write the samples to the Segmenter. // Write the samples to the Segmenter.
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
if (i == 5) { if (i == 5) {
ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment,
kSegmentNumber1));
} }
std::shared_ptr<MediaSample> sample = std::shared_ptr<MediaSample> sample =
CreateSample(kKeyFrame, kDuration, kNoSideData); CreateSample(kKeyFrame, kDuration, kNoSideData);
ASSERT_OK(segmenter_->AddSample(*sample)); ASSERT_OK(segmenter_->AddSample(*sample));
} }
ASSERT_OK( ASSERT_OK(segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration,
segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment)); !kSubsegment, kSegmentNumber2));
ASSERT_OK(segmenter_->Finalize()); ASSERT_OK(segmenter_->Finalize());
// Verify the resulting data. // Verify the resulting data.
@ -205,13 +209,15 @@ TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) {
// Write the samples to the Segmenter. // Write the samples to the Segmenter.
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
if (i == 5) { if (i == 5) {
ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment)); ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment,
kSegmentNumber1));
} }
std::shared_ptr<MediaSample> sample = std::shared_ptr<MediaSample> sample =
CreateSample(kKeyFrame, kDuration, kNoSideData); CreateSample(kKeyFrame, kDuration, kNoSideData);
ASSERT_OK(segmenter_->AddSample(*sample)); 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()); ASSERT_OK(segmenter_->Finalize());
// Verify the resulting data. // Verify the resulting data.
@ -238,7 +244,7 @@ TEST_F(SingleSegmentSegmenterTest, LargeTimestamp) {
ASSERT_OK(segmenter_->AddSample(*sample)); ASSERT_OK(segmenter_->AddSample(*sample));
} }
ASSERT_OK(segmenter_->FinalizeSegment(kLargeTimestamp, 5 * kDuration, ASSERT_OK(segmenter_->FinalizeSegment(kLargeTimestamp, 5 * kDuration,
!kSubsegment)); !kSubsegment, kSegmentNumber1));
ASSERT_OK(segmenter_->Finalize()); ASSERT_OK(segmenter_->Finalize());
// Verify the resulting data. // Verify the resulting data.
@ -274,7 +280,7 @@ TEST_F(SingleSegmentSegmenterTest, ReallyLargeTimestamp) {
ASSERT_OK(segmenter_->AddSample(*sample)); ASSERT_OK(segmenter_->AddSample(*sample));
} }
ASSERT_OK(segmenter_->FinalizeSegment(kReallyLargeTimestamp, 5 * kDuration, ASSERT_OK(segmenter_->FinalizeSegment(kReallyLargeTimestamp, 5 * kDuration,
!kSubsegment)); !kSubsegment, kSegmentNumber1));
ASSERT_OK(segmenter_->Finalize()); ASSERT_OK(segmenter_->Finalize());
// Verify the resulting data. // Verify the resulting data.

View File

@ -82,9 +82,9 @@ Status WebMMuxer::FinalizeSegment(size_t stream_id,
return Status(error::UNIMPLEMENTED, return Status(error::UNIMPLEMENTED,
"Key rotation is not implemented for WebM"); "Key rotation is not implemented for WebM");
} }
return segmenter_->FinalizeSegment(segment_info.start_timestamp, return segmenter_->FinalizeSegment(
segment_info.duration, segment_info.start_timestamp, segment_info.duration,
segment_info.is_subsegment); segment_info.is_subsegment, segment_info.segment_number);
} }
void WebMMuxer::FireOnMediaStartEvent() { void WebMMuxer::FireOnMediaStartEvent() {

View File

@ -39,6 +39,10 @@ const char* kSegmentedFileOutput1 = "memory://output/template-1.vtt";
const char* kSegmentedFileOutput2 = "memory://output/template-2.vtt"; const char* kSegmentedFileOutput2 = "memory://output/template-2.vtt";
const int64_t kSegmentDuration = 10000; const int64_t kSegmentDuration = 10000;
const int64_t kSegmentNumber1 = 1;
const int64_t kSegmentNumber2 = 2;
const float kMillisecondsPerSecond = 1000.0f; const float kMillisecondsPerSecond = 1000.0f;
} // namespace } // namespace
@ -64,13 +68,14 @@ class WebVttMuxerTest : public MediaHandlerTestBase {
}; };
TEST_F(WebVttMuxerTest, WithNoSegmentAndWithNoSamples) { 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. // No segments should have be created as there were no samples.
testing::InSequence s; testing::InSequence s;
EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _));
EXPECT_CALL(*muxer_listener_, OnMediaEndMock(_, _, _, _, _, _, _, _, _)); EXPECT_CALL(*muxer_listener_, OnMediaEndMock(_, _, _, _, _, _, _, _, _));
} }
@ -95,7 +100,7 @@ TEST_F(WebVttMuxerTest, WithOneSegmentAndWithOneSample) {
EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _));
EXPECT_CALL(*muxer_listener_, EXPECT_CALL(*muxer_listener_,
OnNewSegment(kSegmentedFileOutput1, kSegmentStart, OnNewSegment(kSegmentedFileOutput1, kSegmentStart,
kSegmentDuration, _)); kSegmentDuration, _, _));
const float kMediaDuration = 1 * kSegmentDuration / kMillisecondsPerSecond; const float kMediaDuration = 1 * kSegmentDuration / kMillisecondsPerSecond;
EXPECT_CALL(*muxer_listener_, EXPECT_CALL(*muxer_listener_,
@ -112,8 +117,8 @@ TEST_F(WebVttMuxerTest, WithOneSegmentAndWithOneSample) {
ASSERT_OK( ASSERT_OK(
Input(kInputIndex) Input(kInputIndex)
->Dispatch(StreamData::FromSegmentInfo( ->Dispatch(StreamData::FromSegmentInfo(
kStreamIndex, kStreamIndex, GetSegmentInfo(kSegmentStart, kSegmentDuration,
GetSegmentInfo(kSegmentStart, kSegmentDuration, !kEncrypted)))); !kEncrypted, kSegmentNumber1))));
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams()); ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput); ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput);
@ -142,10 +147,10 @@ TEST_F(WebVttMuxerTest, WithTwoSegmentAndWithOneSample) {
EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _));
EXPECT_CALL(*muxer_listener_, EXPECT_CALL(*muxer_listener_,
OnNewSegment(kSegmentedFileOutput1, kSegment1Start, OnNewSegment(kSegmentedFileOutput1, kSegment1Start,
kSegmentDuration, _)); kSegmentDuration, _, _));
EXPECT_CALL(*muxer_listener_, EXPECT_CALL(*muxer_listener_,
OnNewSegment(kSegmentedFileOutput2, kSegment2Start, OnNewSegment(kSegmentedFileOutput2, kSegment2Start,
kSegmentDuration, _)); kSegmentDuration, _, _));
const float kMediaDuration = 2 * kSegmentDuration / kMillisecondsPerSecond; const float kMediaDuration = 2 * kSegmentDuration / kMillisecondsPerSecond;
EXPECT_CALL(*muxer_listener_, EXPECT_CALL(*muxer_listener_,
@ -164,8 +169,8 @@ TEST_F(WebVttMuxerTest, WithTwoSegmentAndWithOneSample) {
ASSERT_OK( ASSERT_OK(
Input(kInputIndex) Input(kInputIndex)
->Dispatch(StreamData::FromSegmentInfo( ->Dispatch(StreamData::FromSegmentInfo(
kStreamIndex, kStreamIndex, GetSegmentInfo(kSegment1Start, kSegmentDuration,
GetSegmentInfo(kSegment1Start, kSegmentDuration, !kEncrypted)))); !kEncrypted, kSegmentNumber1))));
// Segment Two // Segment Two
ASSERT_OK( ASSERT_OK(
Input(kInputIndex) Input(kInputIndex)
@ -174,8 +179,8 @@ TEST_F(WebVttMuxerTest, WithTwoSegmentAndWithOneSample) {
ASSERT_OK( ASSERT_OK(
Input(kInputIndex) Input(kInputIndex)
->Dispatch(StreamData::FromSegmentInfo( ->Dispatch(StreamData::FromSegmentInfo(
kStreamIndex, kStreamIndex, GetSegmentInfo(kSegment2Start, kSegmentDuration,
GetSegmentInfo(kSegment2Start, kSegmentDuration, !kEncrypted)))); !kEncrypted, kSegmentNumber2))));
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams()); ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput1); ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput1);
@ -202,10 +207,10 @@ TEST_F(WebVttMuxerTest, WithAnEmptySegment) {
EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _)); EXPECT_CALL(*muxer_listener_, OnMediaStart(_, _, _, _));
EXPECT_CALL(*muxer_listener_, EXPECT_CALL(*muxer_listener_,
OnNewSegment(kSegmentedFileOutput1, kSegment1Start, OnNewSegment(kSegmentedFileOutput1, kSegment1Start,
kSegmentDuration, _)); kSegmentDuration, _, _));
EXPECT_CALL(*muxer_listener_, EXPECT_CALL(*muxer_listener_,
OnNewSegment(kSegmentedFileOutput2, kSegment2Start, OnNewSegment(kSegmentedFileOutput2, kSegment2Start,
kSegmentDuration, _)); kSegmentDuration, _, _));
const float kMediaDuration = 2 * kSegmentDuration / kMillisecondsPerSecond; const float kMediaDuration = 2 * kSegmentDuration / kMillisecondsPerSecond;
EXPECT_CALL(*muxer_listener_, EXPECT_CALL(*muxer_listener_,
@ -219,8 +224,8 @@ TEST_F(WebVttMuxerTest, WithAnEmptySegment) {
ASSERT_OK( ASSERT_OK(
Input(kInputIndex) Input(kInputIndex)
->Dispatch(StreamData::FromSegmentInfo( ->Dispatch(StreamData::FromSegmentInfo(
kStreamIndex, kStreamIndex, GetSegmentInfo(kSegment1Start, kSegmentDuration,
GetSegmentInfo(kSegment1Start, kSegmentDuration, !kEncrypted)))); !kEncrypted, kSegmentNumber1))));
// Segment Two // Segment Two
ASSERT_OK( ASSERT_OK(
Input(kInputIndex) Input(kInputIndex)
@ -229,8 +234,8 @@ TEST_F(WebVttMuxerTest, WithAnEmptySegment) {
ASSERT_OK( ASSERT_OK(
Input(kInputIndex) Input(kInputIndex)
->Dispatch(StreamData::FromSegmentInfo( ->Dispatch(StreamData::FromSegmentInfo(
kStreamIndex, kStreamIndex, GetSegmentInfo(kSegment2Start, kSegmentDuration,
GetSegmentInfo(kSegment2Start, kSegmentDuration, !kEncrypted)))); !kEncrypted, kSegmentNumber2))));
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams()); ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput1); ASSERT_FILE_STREQ(kSegmentedFileOutput1, kExpectedOutput1);

View File

@ -23,6 +23,7 @@ namespace {
const size_t kStreamIndex = 0; const size_t kStreamIndex = 0;
const bool kSubSegment = true; const bool kSubSegment = true;
const bool kEncrypted = true; const bool kEncrypted = true;
const int64_t kSegmentNumber = 1;
const char* kId1 = "sample-id-1"; const char* kId1 = "sample-id-1";
const char* kId2 = "sample-id-2"; const char* kId2 = "sample-id-2";
@ -95,7 +96,8 @@ class WebVttToMp4HandlerTest : public MediaHandlerTestBase {
const bool kIsSubSegment = true; const bool kIsSubSegment = true;
int64_t duration = end_time - start_time; 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( return In()->Dispatch(
StreamData::FromSegmentInfo(kStreamIndex, std::move(segment))); StreamData::FromSegmentInfo(kStreamIndex, std::move(segment)));
} }

View File

@ -24,6 +24,7 @@ const size_t kOutputCount = 1;
const size_t kInputIndex = 0; const size_t kInputIndex = 0;
const size_t kOutputIndex = 0; const size_t kOutputIndex = 0;
const size_t kStreamIndex = 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 // This value does not matter as trick play does not use it, but it is needed
// to create the audio and video info. // to create the audio and video info.
@ -54,7 +55,8 @@ class TrickPlayHandlerTest : public MediaHandlerTestBase {
Status DispatchSegment(int64_t start_time, int64_t duration) { Status DispatchSegment(int64_t start_time, int64_t duration) {
const bool kSubSegment = true; 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)); auto data = StreamData::FromSegmentInfo(kStreamIndex, std::move(info));
return Input(kInputIndex)->Dispatch(std::move(data)); return Input(kInputIndex)->Dispatch(std::move(data));
} }

View File

@ -22,6 +22,7 @@ namespace shaka {
namespace { namespace {
const char kNoLanguage[] = ""; const char kNoLanguage[] = "";
const uint64_t kAnySegmentNumber = 1;
} // namespace } // namespace
class AdaptationSetTest : public ::testing::Test { class AdaptationSetTest : public ::testing::Test {
@ -682,23 +683,25 @@ TEST_F(OnDemandAdaptationSetTest, SubsegmentAlignment) {
adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo));
// Add a subsegment immediately before adding the 360p Representation. // Add a subsegment immediately before adding the 360p Representation.
// This should still work for VOD. // This should still work for VOD.
representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize,
kAnySegmentNumber);
Representation* representation_360p = Representation* representation_360p =
adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize,
kAnySegmentNumber);
auto aligned = adaptation_set->GetXml(); auto aligned = adaptation_set->GetXml();
EXPECT_THAT(aligned, AttributeEqual("subsegmentAlignment", "true")); EXPECT_THAT(aligned, AttributeEqual("subsegmentAlignment", "true"));
// Unknown because 480p has an extra subsegments. // 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(); auto alignment_unknown = adaptation_set->GetXml();
EXPECT_THAT(alignment_unknown, Not(AttributeSet("subsegmentAlignment"))); EXPECT_THAT(alignment_unknown, Not(AttributeSet("subsegmentAlignment")));
// Add segments that make them not aligned. // Add segments that make them not aligned.
representation_360p->AddNewSegment(10, 1, kAnySize); representation_360p->AddNewSegment(10, 1, kAnySize, kAnySegmentNumber);
representation_360p->AddNewSegment(11, 19, kAnySize); representation_360p->AddNewSegment(11, 19, kAnySize, kAnySegmentNumber);
auto unaligned = adaptation_set->GetXml(); auto unaligned = adaptation_set->GetXml();
EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment"))); EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment")));
@ -740,8 +743,11 @@ TEST_F(OnDemandAdaptationSetTest, ForceSetsubsegmentAlignment) {
static_assert(kStartTime1 != kStartTime2, "StartTimesShouldBeDifferent"); static_assert(kStartTime1 != kStartTime2, "StartTimesShouldBeDifferent");
const int64_t kDuration = 10; const int64_t kDuration = 10;
const uint64_t kAnySize = 19834u; 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(); auto unaligned = adaptation_set->GetXml();
EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment"))); EXPECT_THAT(unaligned, Not(AttributeSet("subsegmentAlignment")));
@ -791,15 +797,17 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignmentDynamicMpd) {
Representation* representation_360p = Representation* representation_360p =
adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize,
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); kAnySegmentNumber);
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize,
kAnySegmentNumber);
auto aligned = adaptation_set->GetXml(); auto aligned = adaptation_set->GetXml();
EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true")); EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true"));
// Add segments that make them not aligned. // Add segments that make them not aligned.
representation_480p->AddNewSegment(11, 20, kAnySize); representation_480p->AddNewSegment(11, 20, kAnySize, kAnySegmentNumber);
representation_360p->AddNewSegment(10, 1, kAnySize); representation_360p->AddNewSegment(10, 1, kAnySize, kAnySegmentNumber);
representation_360p->AddNewSegment(11, 19, kAnySize); representation_360p->AddNewSegment(11, 19, kAnySize, kAnySegmentNumber);
auto unaligned = adaptation_set->GetXml(); auto unaligned = adaptation_set->GetXml();
EXPECT_THAT(unaligned, Not(AttributeSet("segmentAlignment"))); EXPECT_THAT(unaligned, Not(AttributeSet("segmentAlignment")));
@ -844,16 +852,18 @@ TEST_F(LiveAdaptationSetTest, SegmentAlignmentStaticMpd) {
// Representation. // Representation.
Representation* representation_480p = Representation* representation_480p =
adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo));
representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize,
kAnySegmentNumber);
Representation* representation_360p = Representation* representation_360p =
adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize,
kAnySegmentNumber);
representation_480p->AddNewSegment(kStartTime + kDuration, kDuration, representation_480p->AddNewSegment(kStartTime + kDuration, kDuration,
kAnySize); kAnySize, kAnySegmentNumber);
representation_360p->AddNewSegment(kStartTime + kDuration, kDuration, representation_360p->AddNewSegment(kStartTime + kDuration, kDuration,
kAnySize); kAnySize, kAnySegmentNumber);
auto aligned = adaptation_set->GetXml(); auto aligned = adaptation_set->GetXml();
EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true")); EXPECT_THAT(aligned, AttributeEqual("segmentAlignment", "true"));

View File

@ -75,8 +75,11 @@ class MockRepresentation : public Representation {
void(const ContentProtectionElement& element)); void(const ContentProtectionElement& element));
MOCK_METHOD2(UpdateContentProtectionPssh, MOCK_METHOD2(UpdateContentProtectionPssh,
void(const std::string& drm_uuid, const std::string& pssh)); void(const std::string& drm_uuid, const std::string& pssh));
MOCK_METHOD3(AddNewSegment, MOCK_METHOD4(AddNewSegment,
void(int64_t start_time, int64_t duration, uint64_t size)); void(int64_t start_time,
int64_t duration,
uint64_t size,
int64_t segment_number));
MOCK_METHOD0(SetSegmentDuration, void()); MOCK_METHOD0(SetSegmentDuration, void());
MOCK_METHOD0(SetAvailabilityTimeOffset, void()); MOCK_METHOD0(SetAvailabilityTimeOffset, void());
MOCK_METHOD1(SetSampleDuration, void(int32_t sample_duration)); MOCK_METHOD1(SetSampleDuration, void(int32_t sample_duration));

View File

@ -25,11 +25,12 @@ class MockMpdNotifier : public MpdNotifier {
bool(const MediaInfo& media_info, uint32_t* container_id)); bool(const MediaInfo& media_info, uint32_t* container_id));
MOCK_METHOD2(NotifySampleDuration, MOCK_METHOD2(NotifySampleDuration,
bool(uint32_t container_id, int32_t sample_duration)); bool(uint32_t container_id, int32_t sample_duration));
MOCK_METHOD4(NotifyNewSegment, MOCK_METHOD5(NotifyNewSegment,
bool(uint32_t container_id, bool(uint32_t container_id,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t size)); uint64_t size,
int64_t segment_number));
MOCK_METHOD3(NotifyCompletedSegment, MOCK_METHOD3(NotifyCompletedSegment,
bool(uint32_t container_id, int64_t duration, uint64_t size)); bool(uint32_t container_id, int64_t duration, uint64_t size));
MOCK_METHOD1(NotifyAvailabilityTimeOffset, bool(uint32_t container_id)); MOCK_METHOD1(NotifyAvailabilityTimeOffset, bool(uint32_t container_id));

View File

@ -56,6 +56,7 @@ class MpdBuilderTest : public ::testing::Test {
// Not relevant in this test. // Not relevant in this test.
const bool kContentProtectionFlag = true; const bool kContentProtectionFlag = true;
const size_t kBytes = 1000; const size_t kBytes = 1000;
const int64_t kSegmentNumber = 1;
AdaptationSet* adaptation_set = AdaptationSet* adaptation_set =
period->GetOrCreateAdaptationSet(media_info, kContentProtectionFlag); period->GetOrCreateAdaptationSet(media_info, kContentProtectionFlag);
@ -63,7 +64,8 @@ class MpdBuilderTest : public ::testing::Test {
adaptation_set->AddRepresentation(media_info); adaptation_set->AddRepresentation(media_info);
representation->AddNewSegment( representation->AddNewSegment(
segment_start_time_seconds * media_info.reference_time_scale(), 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: protected:

View File

@ -87,11 +87,13 @@ class MpdNotifier {
/// @param duration is the duration of the new segment, in units of the /// @param duration is the duration of the new segment, in units of the
/// stream's time scale. /// stream's time scale.
/// @param size is the new segment size in bytes. /// @param size is the new segment size in bytes.
/// @param segment_number is the segment number.
/// @return true on success, false otherwise. /// @return true on success, false otherwise.
virtual bool NotifyNewSegment(uint32_t container_id, virtual bool NotifyNewSegment(uint32_t container_id,
int64_t start_time, int64_t start_time,
int64_t duration, 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 /// Notifies MpdBuilder that a segment is fully written and provides the
/// segment's complete duration and size. For Low Latency only. Note, size and /// segment's complete duration and size. For Low Latency only. Note, size and

View File

@ -108,10 +108,6 @@ Representation::Representation(
std::move(state_change_listener)) { std::move(state_change_listener)) {
mime_type_ = representation.mime_type_; mime_type_ = representation.mime_type_;
codecs_ = representation.codecs_; codecs_ = representation.codecs_;
start_number_ = representation.start_number_;
for (const SegmentInfo& segment_info : representation.segment_infos_)
start_number_ += segment_info.repeat + 1;
} }
Representation::~Representation() {} Representation::~Representation() {}
@ -172,7 +168,8 @@ void Representation::UpdateContentProtectionPssh(const std::string& drm_uuid,
void Representation::AddNewSegment(int64_t start_time, void Representation::AddNewSegment(int64_t start_time,
int64_t duration, int64_t duration,
uint64_t size) { uint64_t size,
int64_t segment_number) {
if (start_time == 0 && duration == 0) { if (start_time == 0 && duration == 0) {
LOG(WARNING) << "Got segment with start_time and duration == 0. Ignoring."; LOG(WARNING) << "Got segment with start_time and duration == 0. Ignoring.";
return; return;
@ -188,7 +185,7 @@ void Representation::AddNewSegment(int64_t start_time,
if (state_change_listener_) if (state_change_listener_)
state_change_listener_->OnNewSegmentForRepresentation(start_time, duration); 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 // 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 // is completed. In the low latency case, only the first chunk in the segment
@ -307,7 +304,7 @@ std::optional<xml::XmlNode> Representation::GetXml() {
if (HasLiveOnlyFields(media_info_) && if (HasLiveOnlyFields(media_info_) &&
!representation.AddLiveOnlyInfo( !representation.AddLiveOnlyInfo(
media_info_, segment_infos_, start_number_, media_info_, segment_infos_,
mpd_options_.mpd_params.low_latency_dash_mode)) { mpd_options_.mpd_params.low_latency_dash_mode)) {
LOG(ERROR) << "Failed to add Live info."; LOG(ERROR) << "Failed to add Live info.";
return std::nullopt; return std::nullopt;
@ -383,7 +380,9 @@ bool Representation::HasRequiredMediaInfoFields() const {
return true; 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 uint64_t kNoRepeat = 0;
const int64_t adjusted_duration = AdjustDuration(duration); const int64_t adjusted_duration = AdjustDuration(duration);
@ -406,7 +405,8 @@ void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) {
} else { } else {
segment_infos_.push_back( segment_infos_.push_back(
{previous_segment_end_time, {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; return;
} }
@ -431,8 +431,8 @@ void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) {
<< previous_segment_end_time << "."; << previous_segment_end_time << ".";
} }
} }
segment_infos_.push_back(
segment_infos_.push_back({start_time, adjusted_duration, kNoRepeat}); {start_time, adjusted_duration, kNoRepeat, segment_number});
} }
void Representation::UpdateSegmentInfo(int64_t duration) { 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 >= time_shift_buffer_depth) {
current_buffer_depth_ -= last->duration; current_buffer_depth_ -= last->duration;
RemoveOldSegment(&*last); RemoveOldSegment(&*last);
start_number_++;
} }
if (last->repeat >= 0) if (last->repeat >= 0)
break; break;
@ -510,13 +509,15 @@ void Representation::RemoveOldSegment(SegmentInfo* segment_info) {
int64_t segment_start_time = segment_info->start_time; int64_t segment_start_time = segment_info->start_time;
segment_info->start_time += segment_info->duration; segment_info->start_time += segment_info->duration;
segment_info->repeat--; 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) if (mpd_options_.mpd_params.preserved_segments_outside_live_window == 0)
return; return;
segments_to_be_removed_.push_back( segments_to_be_removed_.push_back(
media::GetSegmentName(media_info_.segment_template(), segment_start_time, 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() > while (segments_to_be_removed_.size() >
mpd_options_.mpd_params.preserved_segments_outside_live_window) { mpd_options_.mpd_params.preserved_segments_outside_live_window) {
VLOG(2) << "Deleting " << segments_to_be_removed_.front(); VLOG(2) << "Deleting " << segments_to_be_removed_.front();

View File

@ -99,9 +99,11 @@ class Representation {
/// @param size of the segment in bytes. In the low latency case, this size is /// @param size of the segment in bytes. In the low latency case, this size is
/// that of the /// that of the
/// first chunk because the full size is not yet known. /// 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, virtual void AddNewSegment(int64_t start_time,
int64_t duration, int64_t duration,
uint64_t size); uint64_t size,
int64_t segment_number);
/// Update a media segment in the Representation. /// Update a media segment in the Representation.
/// In the low latency case, the segment duration will not be ready until the /// 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 // Add a SegmentInfo. This function may insert an adjusted SegmentInfo if
// |allow_approximate_segment_timeline_| is set. // |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 // Update the current SegmentInfo. This method is used to update the duration
// value after a low latency segment is complete, and the full segment // value after a low latency segment is complete, and the full segment
@ -248,10 +252,6 @@ class Representation {
BandwidthEstimator bandwidth_estimator_; BandwidthEstimator bandwidth_estimator_;
const MpdOptions& mpd_options_; 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 // If this is not null, then Representation is responsible for calling the
// right methods at right timings. // right methods at right timings.
std::unique_ptr<RepresentationStateChangeListener> state_change_listener_; std::unique_ptr<RepresentationStateChangeListener> state_change_listener_;

View File

@ -277,6 +277,8 @@ TEST_F(RepresentationTest,
const int64_t kStartTime = 199238; const int64_t kStartTime = 199238;
const int64_t kDuration = 98; const int64_t kDuration = 98;
const int64_t kSegmentNumber = 1;
std::unique_ptr<MockRepresentationStateChangeListener> listener( std::unique_ptr<MockRepresentationStateChangeListener> listener(
new MockRepresentationStateChangeListener()); new MockRepresentationStateChangeListener());
EXPECT_CALL(*listener, OnNewSegmentForRepresentation(kStartTime, kDuration)); EXPECT_CALL(*listener, OnNewSegmentForRepresentation(kStartTime, kDuration));
@ -285,7 +287,8 @@ TEST_F(RepresentationTest,
kAnyRepresentationId, std::move(listener)); kAnyRepresentationId, std::move(listener));
EXPECT_TRUE(representation->Init()); EXPECT_TRUE(representation->Init());
representation->AddNewSegment(kStartTime, kDuration, 10 /* any size */); representation->AddNewSegment(kStartTime, kDuration, 10 /* any size */,
kSegmentNumber);
} }
// Make sure // Make sure
@ -463,7 +466,7 @@ class SegmentTemplateTest : public RepresentationTest {
int repeat) { int repeat) {
DCHECK(representation_); DCHECK(representation_);
SegmentInfo s = {start_time, duration, repeat}; SegmentInfo s = {start_time, duration, repeat, segment_number_};
segment_infos_for_expected_out_.push_back(s); segment_infos_for_expected_out_.push_back(s);
if (mpd_options_.mpd_params.low_latency_dash_mode) { 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 // 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. The bandwidth will be updated once the segment is fully
// written and the segment duration and size are known. // written and the segment duration and size are known.
representation_->AddNewSegment(start_time, duration, size); representation_->AddNewSegment(start_time, duration, size,
segment_number_++);
return; return;
} }
@ -484,7 +488,8 @@ class SegmentTemplateTest : public RepresentationTest {
} }
for (int i = 0; i < repeat + 1; ++i) { 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; start_time += duration;
bandwidth_estimator_.AddBlock( bandwidth_estimator_.AddBlock(
size, static_cast<double>(duration) / kDefaultTimeScale); size, static_cast<double>(duration) / kDefaultTimeScale);
@ -524,6 +529,7 @@ class SegmentTemplateTest : public RepresentationTest {
std::list<SegmentInfo> segment_infos_for_expected_out_; std::list<SegmentInfo> segment_infos_for_expected_out_;
std::string expected_s_elements_; std::string expected_s_elements_;
BandwidthEstimator bandwidth_estimator_; BandwidthEstimator bandwidth_estimator_;
int64_t segment_number_ = 1;
}; };
// Estimate the bandwidth given the info from AddNewSegment(). // Estimate the bandwidth given the info from AddNewSegment().
@ -584,12 +590,13 @@ TEST_F(SegmentTemplateTest, RepresentationClone) {
auto cloned_representation = auto cloned_representation =
CopyRepresentation(*representation_, NoListener()); CopyRepresentation(*representation_, NoListener());
const char kExpectedXml[] = const char kExpectedXml[] =
"<Representation id=\"1\" bandwidth=\"0\" " "<Representation id=\"1\" bandwidth=\"0\" "
" codecs=\"avc1.010101\" mimeType=\"video/mp4\" sar=\"1:1\" " " codecs=\"avc1.010101\" mimeType=\"video/mp4\" sar=\"1:1\" "
" width=\"720\" height=\"480\" frameRate=\"10/5\">\n" " width=\"720\" height=\"480\" frameRate=\"10/5\">\n"
" <SegmentTemplate timescale=\"1000\" initialization=\"init.mp4\" " " <SegmentTemplate timescale=\"1000\" initialization=\"init.mp4\" "
" media=\"$Number$.mp4\" startNumber=\"2\">\n" " media=\"$Number$.mp4\" startNumber=\"1\">\n"
" </SegmentTemplate>\n" " </SegmentTemplate>\n"
"</Representation>\n"; "</Representation>\n";
EXPECT_THAT(cloned_representation->GetXml(), XmlNodeEqual(kExpectedXml)); EXPECT_THAT(cloned_representation->GetXml(), XmlNodeEqual(kExpectedXml));

View File

@ -23,6 +23,7 @@ struct SegmentInfo {
// |duration|, then this should be set to 0. The semantics is the same as S@r // |duration|, then this should be set to 0. The semantics is the same as S@r
// in the DASH MPD spec. // in the DASH MPD spec.
int repeat; int repeat;
int64_t start_segment_number;
}; };
} // namespace shaka } // namespace shaka

View File

@ -109,14 +109,15 @@ bool SimpleMpdNotifier::NotifySegmentDuration(uint32_t container_id) {
bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id, bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t size) { uint64_t size,
int64_t segment_number) {
absl::MutexLock lock(&lock_); absl::MutexLock lock(&lock_);
auto it = representation_map_.find(container_id); auto it = representation_map_.find(container_id);
if (it == representation_map_.end()) { if (it == representation_map_.end()) {
LOG(ERROR) << "Unexpected container_id: " << container_id; LOG(ERROR) << "Unexpected container_id: " << container_id;
return false; return false;
} }
it->second->AddNewSegment(start_time, duration, size); it->second->AddNewSegment(start_time, duration, size, segment_number);
return true; return true;
} }

View File

@ -44,7 +44,8 @@ class SimpleMpdNotifier : public MpdNotifier {
bool NotifyNewSegment(uint32_t container_id, bool NotifyNewSegment(uint32_t container_id,
int64_t start_time, int64_t start_time,
int64_t duration, int64_t duration,
uint64_t size) override; uint64_t size,
int64_t segment_number) override;
bool NotifyCompletedSegment(uint32_t container_id, bool NotifyCompletedSegment(uint32_t container_id,
int64_t duration, int64_t duration,
uint64_t size) override; uint64_t size) override;

View File

@ -218,6 +218,8 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewSegment) {
SimpleMpdNotifier notifier(empty_mpd_option_); SimpleMpdNotifier notifier(empty_mpd_option_);
const uint32_t kRepresentationId = 447834u; const uint32_t kRepresentationId = 447834u;
const int64_t kSegmentNumber1 = 1;
std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder()); std::unique_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder());
std::unique_ptr<MockRepresentation> mock_representation( std::unique_ptr<MockRepresentation> mock_representation(
new MockRepresentation(kRepresentationId)); new MockRepresentation(kRepresentationId));
@ -238,10 +240,12 @@ TEST_F(SimpleMpdNotifierTest, NotifyNewSegment) {
const int32_t kSegmentDuration = 100; const int32_t kSegmentDuration = 100;
const uint64_t kSegmentSize = 123456u; const uint64_t kSegmentSize = 123456u;
EXPECT_CALL(*mock_representation, EXPECT_CALL(*mock_representation,
AddNewSegment(kStartTime, kSegmentDuration, kSegmentSize)); AddNewSegment(kStartTime, kSegmentDuration, kSegmentSize,
kSegmentNumber1));
EXPECT_TRUE(notifier.NotifyNewSegment(kRepresentationId, kStartTime, EXPECT_TRUE(notifier.NotifyNewSegment(kRepresentationId, kStartTime,
kSegmentDuration, kSegmentSize)); kSegmentDuration, kSegmentSize,
kSegmentNumber1));
} }
TEST_F(SimpleMpdNotifierTest, NotifyCueEvent) { TEST_F(SimpleMpdNotifierTest, NotifyCueEvent) {

View File

@ -475,9 +475,12 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info,
bool RepresentationXmlNode::AddLiveOnlyInfo( bool RepresentationXmlNode::AddLiveOnlyInfo(
const MediaInfo& media_info, const MediaInfo& media_info,
const std::list<SegmentInfo>& segment_infos, const std::list<SegmentInfo>& segment_infos,
uint32_t start_number,
bool low_latency_dash_mode) { bool low_latency_dash_mode) {
XmlNode segment_template("SegmentTemplate"); XmlNode segment_template("SegmentTemplate");
int start_number =
segment_infos.empty() ? 1 : segment_infos.begin()->start_segment_number;
if (media_info.has_reference_time_scale()) { if (media_info.has_reference_time_scale()) {
RCHECK(segment_template.SetIntegerAttribute( RCHECK(segment_template.SetIntegerAttribute(
"timescale", media_info.reference_time_scale())); "timescale", media_info.reference_time_scale()));

View File

@ -220,7 +220,6 @@ class RepresentationXmlNode : public RepresentationBaseXmlNode {
[[nodiscard]] bool AddLiveOnlyInfo( [[nodiscard]] bool AddLiveOnlyInfo(
const MediaInfo& media_info, const MediaInfo& media_info,
const std::list<SegmentInfo>& segment_infos, const std::list<SegmentInfo>& segment_infos,
uint32_t start_number,
bool low_latency_dash_mode); bool low_latency_dash_mode);
private: private:

View File

@ -370,18 +370,18 @@ class LiveSegmentTimelineTest : public ::testing::Test {
}; };
TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) { TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) {
const uint32_t kStartNumber = 1; const uint32_t kSegmentNumber = 1;
const int64_t kStartTime = 0; const int64_t kStartTime = 0;
const int64_t kDuration = 100; const int64_t kDuration = 100;
const uint64_t kRepeat = 9; const uint64_t kRepeat = 9;
const bool kIsLowLatency = false; const bool kIsLowLatency = false;
std::list<SegmentInfo> segment_infos = { std::list<SegmentInfo> segment_infos = {
{kStartTime, kDuration, kRepeat}, {kStartTime, kDuration, kRepeat, kSegmentNumber},
}; };
RepresentationXmlNode representation; RepresentationXmlNode representation;
ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos,
kStartNumber, kIsLowLatency)); kIsLowLatency));
EXPECT_THAT( EXPECT_THAT(
representation, representation,
@ -392,18 +392,18 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfo) {
} }
TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) { TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) {
const uint32_t kStartNumber = 1;
const int64_t kNonZeroStartTime = 500; const int64_t kNonZeroStartTime = 500;
const int64_t kDuration = 100; const int64_t kDuration = 100;
const uint32_t kSegmentNumber = 1;
const uint64_t kRepeat = 9; const uint64_t kRepeat = 9;
const bool kIsLowLatency = false; const bool kIsLowLatency = false;
std::list<SegmentInfo> segment_infos = { std::list<SegmentInfo> segment_infos = {
{kNonZeroStartTime, kDuration, kRepeat}, {kNonZeroStartTime, kDuration, kRepeat, kSegmentNumber},
}; };
RepresentationXmlNode representation; RepresentationXmlNode representation;
ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos,
kStartNumber, kIsLowLatency)); kIsLowLatency));
EXPECT_THAT(representation, EXPECT_THAT(representation,
XmlNodeEqual( XmlNodeEqual(
@ -417,18 +417,18 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoNonZeroStartTime) {
} }
TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) { TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) {
const uint32_t kStartNumber = 6;
const int64_t kNonZeroStartTime = 500; const int64_t kNonZeroStartTime = 500;
const int64_t kDuration = 100; const int64_t kDuration = 100;
const uint32_t kSegmentNumber = 6;
const uint64_t kRepeat = 9; const uint64_t kRepeat = 9;
const bool kIsLowLatency = false; const bool kIsLowLatency = false;
std::list<SegmentInfo> segment_infos = { std::list<SegmentInfo> segment_infos = {
{kNonZeroStartTime, kDuration, kRepeat}, {kNonZeroStartTime, kDuration, kRepeat, kSegmentNumber},
}; };
RepresentationXmlNode representation; RepresentationXmlNode representation;
ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos,
kStartNumber, kIsLowLatency)); kIsLowLatency));
EXPECT_THAT( EXPECT_THAT(
representation, representation,
@ -439,8 +439,9 @@ TEST_F(LiveSegmentTimelineTest, OneSegmentInfoMatchingStartTimeAndNumber) {
} }
TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) { TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) {
const uint32_t kStartNumber = 1;
const bool kIsLowLatency = false; const bool kIsLowLatency = false;
const uint32_t kSegmentNumber1 = 1;
const uint32_t kSegmentNumber11 = 11;
const int64_t kStartTime1 = 0; const int64_t kStartTime1 = 0;
const int64_t kDuration1 = 100; const int64_t kDuration1 = 100;
@ -451,12 +452,12 @@ TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) {
const uint64_t kRepeat2 = 0; const uint64_t kRepeat2 = 0;
std::list<SegmentInfo> segment_infos = { std::list<SegmentInfo> segment_infos = {
{kStartTime1, kDuration1, kRepeat1}, {kStartTime1, kDuration1, kRepeat1, kSegmentNumber1},
{kStartTime2, kDuration2, kRepeat2}, {kStartTime2, kDuration2, kRepeat2, kSegmentNumber11},
}; };
RepresentationXmlNode representation; RepresentationXmlNode representation;
ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos,
kStartNumber, kIsLowLatency)); kIsLowLatency));
EXPECT_THAT( EXPECT_THAT(
representation, representation,
@ -467,8 +468,9 @@ TEST_F(LiveSegmentTimelineTest, AllSegmentsSameDurationExpectLastOne) {
} }
TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) { TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) {
const uint32_t kStartNumber = 1;
const bool kIsLowLatency = false; const bool kIsLowLatency = false;
const uint32_t kSegmentNumber1 = 1;
const uint32_t kSegmentNumber11 = 11;
const int64_t kStartTime1 = 0; const int64_t kStartTime1 = 0;
const int64_t kDuration1 = 100; const int64_t kDuration1 = 100;
@ -479,12 +481,12 @@ TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) {
const uint64_t kRepeat2 = 1; const uint64_t kRepeat2 = 1;
std::list<SegmentInfo> segment_infos = { std::list<SegmentInfo> segment_infos = {
{kStartTime1, kDuration1, kRepeat1}, {kStartTime1, kDuration1, kRepeat1, kSegmentNumber1},
{kStartTime2, kDuration2, kRepeat2}, {kStartTime2, kDuration2, kRepeat2, kSegmentNumber11},
}; };
RepresentationXmlNode representation; RepresentationXmlNode representation;
ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos,
kStartNumber, kIsLowLatency)); kIsLowLatency));
EXPECT_THAT(representation, EXPECT_THAT(representation,
XmlNodeEqual( XmlNodeEqual(
@ -499,8 +501,9 @@ TEST_F(LiveSegmentTimelineTest, SecondSegmentInfoNonZeroRepeat) {
} }
TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) { TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) {
const uint32_t kStartNumber = 1;
const bool kIsLowLatency = false; const bool kIsLowLatency = false;
const uint32_t kSegmentNumber1 = 1;
const uint32_t kSegmentNumber11 = 11;
const int64_t kStartTime1 = 0; const int64_t kStartTime1 = 0;
const int64_t kDuration1 = 100; const int64_t kDuration1 = 100;
@ -512,12 +515,12 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) {
const uint64_t kRepeat2 = 0; const uint64_t kRepeat2 = 0;
std::list<SegmentInfo> segment_infos = { std::list<SegmentInfo> segment_infos = {
{kStartTime1, kDuration1, kRepeat1}, {kStartTime1, kDuration1, kRepeat1, kSegmentNumber1},
{kStartTime2, kDuration2, kRepeat2}, {kStartTime2, kDuration2, kRepeat2, kSegmentNumber11},
}; };
RepresentationXmlNode representation; RepresentationXmlNode representation;
ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos,
kStartNumber, kIsLowLatency)); kIsLowLatency));
EXPECT_THAT(representation, EXPECT_THAT(representation,
XmlNodeEqual( XmlNodeEqual(
@ -532,14 +535,14 @@ TEST_F(LiveSegmentTimelineTest, TwoSegmentInfoWithGap) {
} }
TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) { TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) {
const uint32_t kStartNumber = 1;
const int64_t kStartTime = 0; const int64_t kStartTime = 0;
const int64_t kDuration = 100; const int64_t kDuration = 100;
const uint32_t kSegmentNumber = 1;
const uint64_t kRepeat = 9; const uint64_t kRepeat = 9;
const bool kIsLowLatency = false; const bool kIsLowLatency = false;
std::list<SegmentInfo> segment_infos = { std::list<SegmentInfo> segment_infos = {
{kStartTime, kDuration, kRepeat}, {kStartTime, kDuration, kRepeat, kSegmentNumber},
}; };
RepresentationXmlNode representation; RepresentationXmlNode representation;
FlagSaver<bool> segment_number_saver( FlagSaver<bool> segment_number_saver(
@ -547,7 +550,7 @@ TEST_F(LiveSegmentTimelineTest, LastSegmentNumberSupplementalProperty) {
absl::SetFlag(&FLAGS_dash_add_last_segment_number_when_needed, true); absl::SetFlag(&FLAGS_dash_add_last_segment_number_when_needed, true);
ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos,
kStartNumber, kIsLowLatency)); kIsLowLatency));
EXPECT_THAT( EXPECT_THAT(
representation, representation,
@ -749,11 +752,11 @@ TEST_F(LowLatencySegmentTest, LowLatencySegmentTemplate) {
const bool kIsLowLatency = true; const bool kIsLowLatency = true;
std::list<SegmentInfo> segment_infos = { std::list<SegmentInfo> segment_infos = {
{kStartNumber, kDuration, kRepeat}, {kStartNumber, kDuration, kRepeat, kStartNumber},
}; };
RepresentationXmlNode representation; RepresentationXmlNode representation;
ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos, ASSERT_TRUE(representation.AddLiveOnlyInfo(media_info_, segment_infos,
kStartNumber, kIsLowLatency)); kIsLowLatency));
EXPECT_THAT( EXPECT_THAT(
representation, representation,
XmlNodeEqual("<Representation>" XmlNodeEqual("<Representation>"

View File

@ -275,6 +275,11 @@ Status ValidateParams(const PackagingParams& packaging_params,
"subsegment_sap_aligned to true is not allowed."); "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()) { if (stream_descriptors.empty()) {
return Status(error::INVALID_ARGUMENT, return Status(error::INVALID_ARGUMENT,
"Stream descriptors cannot be empty."); "Stream descriptors cannot be empty.");
@ -517,8 +522,8 @@ std::unique_ptr<MediaHandler> CreateTextChunker(
const ChunkingParams& chunking_params) { const ChunkingParams& chunking_params) {
const float segment_length_in_seconds = const float segment_length_in_seconds =
chunking_params.segment_duration_in_seconds; chunking_params.segment_duration_in_seconds;
return std::unique_ptr<MediaHandler>( return std::unique_ptr<MediaHandler>(new TextChunker(
new TextChunker(segment_length_in_seconds)); segment_length_in_seconds, chunking_params.start_segment_number));
} }
Status CreateTtmlJobs( Status CreateTtmlJobs(