Skip to content

Commit

Permalink
feat: get start number from muxer and specify initial sequence number (
Browse files Browse the repository at this point in the history
…#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>
  • Loading branch information
sr1990 and cosmin committed May 2, 2024
1 parent 62f861c commit bb104fe
Show file tree
Hide file tree
Showing 91 changed files with 538 additions and 359 deletions.
4 changes: 4 additions & 0 deletions docs/source/options/chunking_options.rst
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions include/packager/chunking_params.h
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions packager/app/muxer_flags.cc
Expand Up @@ -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.");
1 change: 1 addition & 0 deletions packager/app/muxer_flags.h
Expand Up @@ -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_
2 changes: 2 additions & 0 deletions packager/app/packager_main.cc
Expand Up @@ -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;
Expand Down
14 changes: 12 additions & 2 deletions packager/app/test/packager_test.py
Expand Up @@ -485,8 +485,8 @@ def _GetFlags(self,
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:
Expand Down Expand Up @@ -575,6 +575,9 @@ def _GetFlags(self,
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]

Expand Down Expand Up @@ -823,6 +826,13 @@ def testForcedSubtitle(self):
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),
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
28 changes: 28 additions & 0 deletions packager/app/test/testdata/dash-start-number/output.mpd
@@ -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>
6 changes: 3 additions & 3 deletions packager/hls/base/media_playlist.cc
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions packager/media/base/media_handler.h
Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion packager/media/base/media_handler_test_base.cc
Expand Up @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion packager/media/base/media_handler_test_base.h
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion packager/media/base/muxer.h
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packager/media/base/muxer_util.cc
Expand Up @@ -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));

Expand Down Expand Up @@ -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") {
Expand Down
4 changes: 2 additions & 2 deletions packager/media/base/muxer_util.h
Expand Up @@ -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
Expand Down
98 changes: 30 additions & 68 deletions packager/media/base/muxer_util_unittest.cc
Expand Up @@ -62,95 +62,57 @@ TEST(MuxerUtilTest, ValidateSegmentTemplateWithFormatTag) {

TEST(MuxerUtilTest, GetSegmentName) {
const int64_t kSegmentStartTime = 180180;
const uint32_t kSegmentIndex = 11;
const uint32_t kSegmentNumber = 12;
const uint32_t kBandwidth = 1234;
EXPECT_EQ("12", GetSegmentName("$Number$",
kSegmentStartTime,
kSegmentIndex,
EXPECT_EQ("12", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber,
kBandwidth));
EXPECT_EQ("012",
GetSegmentName("$Number%03d$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ(
"12$foo$00012",
GetSegmentName(
"$Number%01d$$$foo$$$Number%05d$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));

EXPECT_EQ("180180",
GetSegmentName("$Time$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("012", GetSegmentName("$Number%03d$", kSegmentStartTime,
kSegmentNumber, kBandwidth));
EXPECT_EQ("12$foo$00012",
GetSegmentName("$Number%01d$$$foo$$$Number%05d$", kSegmentStartTime,
kSegmentNumber, kBandwidth));

EXPECT_EQ("180180", GetSegmentName("$Time$", kSegmentStartTime,
kSegmentNumber, kBandwidth));
EXPECT_EQ("foo$_$18018000180180.m4s",
GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
kSegmentStartTime, kSegmentNumber, kBandwidth));

// Combo values.
EXPECT_EQ("12-1234",
GetSegmentName("$Number$-$Bandwidth$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("12-1234", GetSegmentName("$Number$-$Bandwidth$", kSegmentStartTime,
kSegmentNumber, kBandwidth));
EXPECT_EQ("012-001234",
GetSegmentName("$Number%03d$-$Bandwidth%06d$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
GetSegmentName("$Number%03d$-$Bandwidth%06d$", kSegmentStartTime,
kSegmentNumber, kBandwidth));

// Format specifier edge cases.
EXPECT_EQ("12",
GetSegmentName("$Number%00d$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("00012",
GetSegmentName("$Number%005d$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("12", GetSegmentName("$Number%00d$", kSegmentStartTime,
kSegmentNumber, kBandwidth));
EXPECT_EQ("00012", GetSegmentName("$Number%005d$", kSegmentStartTime,
kSegmentNumber, kBandwidth));
}

TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) {
const int64_t kSegmentStartTime = 0;
const uint32_t kSegmentIndex = 0;
const uint32_t kSegmentNumber = 1;
const uint32_t kBandwidth = 0;
EXPECT_EQ("1", GetSegmentName("$Number$",
kSegmentStartTime,
kSegmentIndex,
EXPECT_EQ("1", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber,
kBandwidth));
EXPECT_EQ("001",
GetSegmentName("$Number%03d$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));

EXPECT_EQ("0", GetSegmentName("$Time$",
kSegmentStartTime,
kSegmentIndex,
EXPECT_EQ("001", GetSegmentName("$Number%03d$", kSegmentStartTime,
kSegmentNumber, kBandwidth));

EXPECT_EQ("0", GetSegmentName("$Time$", kSegmentStartTime, kSegmentNumber,
kBandwidth));
EXPECT_EQ("00000000.m4s",
GetSegmentName("$Time%08d$.m4s",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("00000000.m4s", GetSegmentName("$Time%08d$.m4s", kSegmentStartTime,
kSegmentNumber, kBandwidth));
}

TEST(MuxerUtilTest, GetSegmentNameLargeTime) {
const int64_t kSegmentStartTime = 1601599839840ULL;
const uint32_t kSegmentIndex = 8888888;
const uint32_t kSegmentNumber = 8888889;
const uint32_t kBandwidth = 444444;
EXPECT_EQ("1601599839840",
GetSegmentName("$Time$",
kSegmentStartTime,
kSegmentIndex,
kBandwidth));
EXPECT_EQ("1601599839840", GetSegmentName("$Time$", kSegmentStartTime,
kSegmentNumber, kBandwidth));
}

} // namespace media
Expand Down
15 changes: 11 additions & 4 deletions packager/media/base/text_muxer.cc
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down
1 change: 0 additions & 1 deletion packager/media/base/text_muxer.h
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion packager/media/chunking/chunking_handler.cc
Expand Up @@ -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() {
Expand Down Expand Up @@ -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));
}

Expand Down

0 comments on commit bb104fe

Please sign in to comment.