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
*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,
/// decoupling latency from segment duration.
bool low_latency_dash_mode = false;
/// Indicates the startNumber in DASH SegmentTemplate and HLS segment name.
int64_t start_segment_number = 1;
};
} // namespace shaka

View File

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

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(int32_t, transport_stream_timestamp_offset_ms);
ABSL_DECLARE_FLAG(int32_t, default_text_zero_bias_ms);
ABSL_DECLARE_FLAG(int64_t, start_segment_number);
#endif // APP_MUXER_FLAGS_H_

View File

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

View File

@ -485,8 +485,8 @@ class PackagerAppTest(unittest.TestCase):
use_fake_clock=True,
allow_codec_switching=False,
dash_force_segment_list=False,
force_cl_index=None):
force_cl_index=None,
start_segment_number=None):
flags = ['--single_threaded']
if not strip_parameter_set_nalus:
@ -575,6 +575,9 @@ class PackagerAppTest(unittest.TestCase):
elif force_cl_index is False:
flags += ['--noforce_cl_index']
if start_segment_number is not None:
flags += ['--start_segment_number', str(start_segment_number)]
if ad_cues:
flags += ['--ad_cues', ad_cues]
@ -823,6 +826,13 @@ class PackagerFunctionalTest(PackagerAppTest):
output_hls=True))
self._CheckTestResults('forced-subtitle')
def testDashStartNumber(self):
audio_video_streams = self._GetStreams(['audio', 'video'], segmented=True)
streams = audio_video_streams
self.assertPackageSuccess(streams, self._GetFlags(output_dash=True,
start_segment_number=0))
self._CheckTestResults('dash-start-number')
def testAudioVideoWithLanguageOverride(self):
self.assertPackageSuccess(
self._GetStreams(['audio', 'video'], language='por', hls=True),

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)
return;
segments_to_be_removed_.push_back(
media::GetSegmentName(media_info_.segment_template(), start_time,
media_sequence_number_, media_info_.bandwidth()));
segments_to_be_removed_.push_back(media::GetSegmentName(
media_info_.segment_template(), start_time, media_sequence_number_ + 1,
media_info_.bandwidth()));
while (segments_to_be_removed_.size() >
hls_params_.preserved_segments_outside_live_window) {
VLOG(2) << "Deleting " << segments_to_be_removed_.front();

View File

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

View File

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

View File

@ -325,7 +325,8 @@ class MediaHandlerTestBase : public ::testing::Test {
std::unique_ptr<SegmentInfo> GetSegmentInfo(int64_t start_timestamp,
int64_t duration,
bool is_subsegment) const;
bool is_subsegment,
int64_t segment_number) 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
// be a template. In this case, there will be NumAdCues + 1 files generated.
std::string output_file_template_;
size_t output_file_index_ = 0;
size_t output_file_index_ = 1;
};
} // namespace media

View File

@ -110,7 +110,7 @@ Status ValidateSegmentTemplate(const std::string& segment_template) {
std::string GetSegmentName(const std::string& segment_template,
int64_t segment_start_time,
uint32_t segment_index,
uint32_t segment_number,
uint32_t bandwidth) {
DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template));
@ -154,7 +154,7 @@ std::string GetSegmentName(const std::string& segment_template,
absl::UntypedFormatSpec format(format_tag);
if (identifier == "Number") {
// SegmentNumber starts from 1.
format_args.emplace_back(static_cast<uint64_t>(segment_index + 1));
format_args.emplace_back(static_cast<uint64_t>(segment_number));
} else if (identifier == "Time") {
format_args.emplace_back(static_cast<uint64_t>(segment_start_time));
} 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
/// comply with ISO/IEC 23009-1:2012 5.3.9.4.4.
/// @param segment_start_time specifies the segment start time.
/// @param segment_index specifies the segment index.
/// @param segment_number specifies the segment number.
/// @param bandwidth represents the bit rate, in bits/sec, of the stream.
/// @return The segment name with identifier substituted.
std::string GetSegmentName(const std::string& segment_template,
int64_t segment_start_time,
uint32_t segment_index,
uint32_t segment_number,
uint32_t bandwidth);
} // namespace media

View File

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

View File

@ -50,9 +50,13 @@ Status TextMuxer::Finalize() {
// Insert a dummy value so the HLS generator will generate a segment list.
ranges.subsegment_ranges.emplace_back();
// The segment number does not matter for single segment output.
const uint32_t kArbitrarySegmentNumber = 0;
muxer_listener()->OnNewSegment(
options().output_file_name, 0,
duration_seconds * streams()[0]->time_scale(), size);
duration_seconds * streams()[0]->time_scale(), size,
kArbitrarySegmentNumber);
}
muxer_listener()->OnMediaEnd(ranges, duration_seconds);
@ -82,17 +86,20 @@ Status TextMuxer::FinalizeSegment(size_t stream_id,
const std::string& segment_template = options().segment_template;
DCHECK(!segment_template.empty());
const uint32_t index = segment_index_++;
const int64_t start = segment_info.start_timestamp;
const int64_t duration = segment_info.duration;
const uint32_t segment_number = segment_info.segment_number;
const uint32_t bandwidth = options().bandwidth;
const std::string filename =
GetSegmentName(segment_template, start, index, bandwidth);
GetSegmentName(segment_template, start, segment_number, bandwidth);
uint64_t size;
RETURN_IF_ERROR(WriteToFile(filename, &size));
muxer_listener()->OnNewSegment(filename, start, duration, size);
muxer_listener()->OnNewSegment(filename, start, duration, size,
segment_number);
return Status::OK;
}

View File

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

View File

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

View File

@ -60,7 +60,7 @@ class ChunkingHandler : public MediaHandler {
Status OnCueEvent(std::shared_ptr<const CueEvent> event);
Status OnMediaSample(std::shared_ptr<const MediaSample> sample);
Status EndSegmentIfStarted() const;
Status EndSegmentIfStarted();
Status EndSubsegmentIfStarted() const;
bool IsSubsegmentEnabled() {
@ -74,8 +74,13 @@ class ChunkingHandler : public MediaHandler {
int64_t segment_duration_ = 0;
int64_t subsegment_duration_ = 0;
// Segment number that keeps monotically increasing.
// Set to start_segment_number in constructor.
int64_t segment_number_ = 1;
// Current segment index, useful to determine where to do chunking.
int64_t current_segment_index_ = -1;
// Current subsegment index, useful to determine where to do chunking.
int64_t current_subsegment_index_ = -1;

View File

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

View File

@ -20,7 +20,8 @@ namespace media {
// is when a cue event is seen.
class TextChunker : public MediaHandler {
public:
explicit TextChunker(double segment_duration_in_seconds);
explicit TextChunker(double segment_duration_in_seconds,
int64_t start_segment_number);
private:
TextChunker(const TextChunker&) = delete;
@ -52,6 +53,10 @@ class TextChunker : public MediaHandler {
int64_t segment_start_ = -1; // Set when the first sample comes in.
int64_t segment_duration_ = -1; // Set in OnStreamInfo.
// Segment number that keeps monotically increasing.
// Set to start_segment_number in constructor.
int64_t segment_number_ = 1;
// All samples that make up the current segment. We must store the samples
// until the segment ends because a cue event may end the segment sooner
// than we expected.

View File

@ -30,6 +30,8 @@ const size_t kOutput = 0;
const bool kEncrypted = true;
const bool kSubSegment = true;
const int64_t kStartSegmentNumber = 1;
const char* kNoId = "";
const char* kNoPayload = "";
} // namespace
@ -38,7 +40,8 @@ class TextChunkerTest : public MediaHandlerTestBase {
protected:
Status Init(double segment_duration) {
return SetUpAndInitializeGraph(
std::make_shared<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))));
ASSERT_OK(Process(StreamData::FromSegmentInfo(
kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration,
!kIsSubsegment))));
!kIsSubsegment, i + 1))));
const bool is_encrypted = i == 2;
const auto& output_stream_data = GetOutputStreamDataVector();
EXPECT_THAT(output_stream_data,
@ -436,7 +436,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) {
kIsKeyFrame, kData, kDataSize))));
ASSERT_OK(Process(StreamData::FromSegmentInfo(
kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration,
!kIsSubsegment))));
!kIsSubsegment, i))));
const bool is_encrypted = i >= 2;
const auto& output_stream_data = GetOutputStreamDataVector();
EXPECT_THAT(output_stream_data,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -133,10 +133,12 @@ class MuxerListener {
/// @param duration is the duration of the segment, relative to the timescale
/// specified by MediaInfo passed to OnMediaStart().
/// @param segment_file_size is the segment size in bytes.
/// @param segment_number is the segment number.
virtual void OnNewSegment(const std::string& segment_name,
int64_t start_time,
int64_t duration,
uint64_t segment_file_size) = 0;
uint64_t segment_file_size,
int64_t segment_number) = 0;
/// Called when a segment has been muxed and the entire file has been written.
/// For Low Latency only. Note that it should be called after OnNewSegment.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -84,6 +84,7 @@ class MockTsWriter : public TsWriter {
MOCK_METHOD1(NewSegment, bool(BufferWriter* buffer_writer));
MOCK_METHOD0(SignalEncrypted, void());
MOCK_METHOD0(FinalizeSegment, bool());
// Similar to the hack above but takes a std::unique_ptr.
MOCK_METHOD2(AddPesPacketMock, bool(PesPacket* pes_packet,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -58,7 +58,8 @@ class Segmenter {
/// Finalize the (sub)segment.
virtual Status FinalizeSegment(int64_t start_timestamp,
int64_t duration_timestamp,
bool is_subsegment) = 0;
bool is_subsegment,
int64_t segment_number) = 0;
/// @return true if there is an initialization range, while setting @a start
/// and @a end; or false if initialization range does not apply.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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