-
Notifications
You must be signed in to change notification settings - Fork 511
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Omit SegmentTimeline completely, with allow_approximate_segment_timel… #747
base: main
Are you sure you want to change the base?
Conversation
packager/mpd/base/representation.cc
Outdated
@@ -332,6 +332,10 @@ void Representation::AddSegmentInfo(int64_t start_time, int64_t duration) { | |||
const uint64_t kNoRepeat = 0; | |||
const int64_t adjusted_duration = AdjustDuration(duration); | |||
|
|||
if (segment_infos_.empty()) { | |||
start_number_ = start_time / duration; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initial start_number should propagate from the muxer, but we can worry about that later.
The duration here should be scaled_target_duration:
const int64_t scaled_target_duration =
mpd_options_.mpd_params.target_segment_duration *
media_info_.reference_time_scale();
start_number_ = start_time / scaled_target_duration + 1;
Also, to simplify the handling later, let's just exclude the small first segment if there is. Combined with the logic above, the code would be:
if (stream_just_started_ && segment_infos_.size() == 2) {
const SegmentInfo& first_segment = segment_infos.front();
const SegmentInfo& last_segment = segment_infos.back();
if (first_segment.repeat == 0 && first_segment.duration < last_segment.duration && last_segment.repeat > 0) {
segment_infos_.pop_front();
start_number_ += 1;
}
stream_just_started_ = false;
}
packager/mpd/base/xml/xml_node.cc
Outdated
@@ -56,14 +64,15 @@ bool IsTimelineConstantDuration(const std::list<SegmentInfo>& segment_infos, | |||
return false; | |||
|
|||
const SegmentInfo& first_segment = segment_infos.front(); | |||
if (first_segment.start_time != first_segment.duration * (start_number - 1)) | |||
if (!ApproximiatelyEqual(first_segment.start_time, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be difficult to be approximately equal with 5% error for live streaming.
I suggest just checking:
if (first_segment.start_time / first_segment.duration != start_number - 1)
return false;
packager/mpd/base/xml/xml_node.cc
Outdated
return false; | ||
|
||
if (segment_infos.size() == 1) | ||
return true; | ||
|
||
const SegmentInfo& last_segment = segment_infos.back(); | ||
if (last_segment.repeat != 0) | ||
if (!(last_segment.repeat == 0 || first_segment.repeat == 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change will not be needed with the initial shorter segment removed.
packager/mpd/base/representation.cc
Outdated
if (adjusted_duration == scaled_target_duration) { | ||
start_number_ = start_time / scaled_target_duration + 1; | ||
} else { | ||
start_number_ = start_time / scaled_target_duration; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should always be: start_number_ = start_time / scaled_target_duration + 1 as start number is one-based.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review. Yes you are right. The reason to add this was to have the calculated start segment equal to segment_index which is calculated in ChunkingHandler::OnMediaSample. For example, following is the case where start number is calculated with only start_time/scaled_target_duration for shorter duration segment:
[0412/235722:ERROR:chunking_handler.cc(103)] -> Segment Index: 5488
[0412/235724:ERROR:chunking_handler.cc(103)] -> Segment Index: 5488
[0412/235726:ERROR:chunking_handler.cc(103)] -> Segment Index: 5489
[0412/235726:ERROR:representation.cc(348)] ->: start_number: 5488
If start_number_ = start_time / scaled_target_duration +1; for segments that are smaller than the target_duration provided, won't it set the start number to the next segment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Start number is one-based and segment index is zero-based, so start number is expected to be segment index + 1.
Also note that this is a temporary code, we'll need to get start number from muxer ultimately. We can discuss about that later when this code is approved working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
start_number is one based while segment index is zero based, so it is expected that start number = segment index + 1.
Note that this is a temporary code as I mentioned earlier. We'll need to get start number from muxer ultimately, which we can discuss later once we got this piece working.
packager/mpd/base/xml/xml_node.cc
Outdated
if (first_segment.duration < target_duration) { | ||
if (static_cast<uint32_t>(first_segment.start_time / target_duration) != | ||
start_number) | ||
return false; | ||
} else { | ||
if (static_cast<uint32_t>(first_segment.start_time / | ||
first_segment.duration) != start_number - 1) | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic is incorrect. We'll need to handle the possible short segment here if we do not want to remove it earlier. There are a few problems with the code above. For example, the code will return false in line 76 when there is a shorter initial segment.
We can handle it properly with:
bool GetTimelineConstantDuration(const std::list<SegmentInfo>& segment_infos,
uint32_t start_number,
int64_t* constant_duration) {
if (!FLAGS_segment_template_constant_duration)
return false;
DCHECK(!segment_infos.empty());
if (segment_infos.size() > 2)
return false;
int64_t first_segment_timestamp = segment_infos.front().start_time;
if (segment_infos.size() == 1) {
*constant_duration = segment_infos.front().duration;
return first_segment_timestamp / *constant_duration == start_number - 1;
}
if (segment_infos.front().repeat != 0 && segment_infos.back().repeat() != 0)
return false;
int64_t last_segment_timestamp = segment_infos.back().start_time;
int64_t last_segment_start_number = start_number + segment_infos.front().repeat + 1;
*constant_duration =
segment_infos.front().repeat == 0 ? segment_infos.back().duration : // shorter initial segment
segment_infos.front().duration; // shorter ending segment
return first_segment_timestamp / *constant_duration == start_number - 1 &&
last_segment_timestamp / *constant_duration == last_segment_start_number - 1;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After adding
if (segment_infos.size() == 1) {
*constant_duration = segment_infos.front().duration;
return first_segment_timestamp / *constant_duration == start_number - 1;
}
and the first segment is shorter than target segment, then won't the calculated start time in representation.cc (start_time / scaled_target_duration + 1) be different than the one that it is compared against here? Also if we calculate the start number based on smaller duration than it will be greater than one calculated in ChunkingHandler::OnMediaSample and representation.cc.
For example, the code will return false in line 76 when there is a shorter initial segment.
As per my understanding:
- If smaller segment is added, then IsTimelineConstantDuration will return true at 72.
- If there is a target duration segment added, then IsTimelineConstantDuration will return true at 82.
- Then as soon as the target duration repeat count goes above 0, the code will pop out the first shorter segment and in this case, the method will return true at line 72 and won't reach 76.
Can you please clarify?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then as soon as the target duration repeat count goes above 0, the code will pop out the first shorter segment and in this case, the method will return true at line 72 and won't reach 76.
The initial shorter segment will not be removed until the live window is filled up.
Let's assume a buffer depth of 10000 and target duration of 1000.
(1) Initially there is only the initial shorter segment: S1 (timestamp, duration) where timestamp = 10500, duration = 500
(2) S2 (11000, 1000)
(3) S3 (12000, 1000)
(4) S4 (13000, 1000)
...
(10) S10 (19000, 1000)
(11) S11 (20000, 1000)
(12) S12 (21000, 1000)
(13) S12 (22000, 1000)
The start number is 10500 / 1000 + 1 = 11.
segment_infos is
(1) [(timestamp, duration, repeat)], where timestamp = 10500, duration = 500, repeat = 0
(2) [(10500, 500, 0), (11000, 1000, 0)]
(3) [(10500, 500, 0), (11000, 1000, 1)]
(4) [(10500, 500, 0), (11000, 1000, 2)]
...
(10) [(10500, 500, 0), (11000, 1000, 8)]
(11) [(10500, 500, 0), (11000, 1000, 9)]
(12) [(11000, 1000, 10)]
(13) [(12000, 1000, 10)]
With your code,
(1) True, constant_duration = 1000
(2) True, constant_duration = 1000
(3) False
(4) False
...
(10) False
(11) False
(12) True, constant_duration = 1000
(13) True, constant_duration = 1000
With my proposed code,
(1) False
(2) True, constant_duration = 1000
(3) True, constant_duration = 1000
(4) True, constant_duration = 1000
...
(10) True, constant_duration = 1000
(11) True, constant_duration = 1000
(12) True, constant_duration = 1000
(13) True, constant_duration = 1000
You are right that (1) fails in my proposed code, which I intentionally left out as I don't want the code to be dependent on another parameter. If you feel strongly about it, we can also merge your solution and my solution so it returns true in all cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the example. By popping out, I meant code added at representation.cc::368 to 380 to pop the first short segment if approximate_segment_timeline and target_segment_duration are set. At (3), the first shorter segment will be popped out and only [(11000, 1000, 1] will be present and True will be returned from line 72 in xml_node.cc.
After trying out the changes with flags set to --mpd_output live.mpd --allow_approximate_segment_timeline --segment_template_constant_duration --segment_duration 10 --time_shift_buffer_depth 50, I get the following values of segment_infos_:
[0413/204712:ERROR:representation.cc(172)] SEGMENT_INFO List:
[0413/204712:ERROR:representation.cc(175)] 0 Start Time: 1798126434 Duration: 180000 Repeat: 0 -> shorter 2 seconds segment
[0413/204722:ERROR:representation.cc(172)] SEGMENT_INFO List:
[0413/204722:ERROR:representation.cc(175)] 0 Start Time: 1798126434 Duration: 180000 Repeat: 0
[0413/204722:ERROR:representation.cc(175)] 1 Start Time: 1798306434 Duration: 900000 Repeat: 0
[0413/204732:ERROR:representation.cc(172)] SEGMENT_INFO List:
[0413/204732:ERROR:representation.cc(175)] 0 Start Time: 1798306434 Duration: 900000 Repeat: 1 -> shorter segment popped out
[0413/204742:ERROR:representation.cc(172)] SEGMENT_INFO List:
[0413/204742:ERROR:representation.cc(175)] 0 Start Time: 1798306434 Duration: 900000 Repeat: 2
[0413/204752:ERROR:representation.cc(172)] SEGMENT_INFO List:
[0413/204752:ERROR:representation.cc(175)] 0 Start Time: 1798306434 Duration: 900000 Repeat: 3
[0413/204802:ERROR:representation.cc(172)] SEGMENT_INFO List:
[0413/204802:ERROR:representation.cc(175)] 0 Start Time: 1798306434 Duration: 900000 Repeat: 4
[0413/204812:ERROR:representation.cc(172)] SEGMENT_INFO List:
[0413/204812:ERROR:representation.cc(175)] 0 Start Time: 1798306434 Duration: 900000 Repeat: 5
[0413/204822:ERROR:representation.cc(172)] SEGMENT_INFO List:
[0413/204822:ERROR:representation.cc(175)] 0 Start Time: 1799206434 Duration: 900000 Repeat: 5
[0413/204832:ERROR:representation.cc(172)] SEGMENT_INFO List:
[0413/204832:ERROR:representation.cc(175)] 0 Start Time: 1800106434 Duration: 900000 Repeat: 5 -> repeat count remains 5 due to tsbd and start time increases.
Is this correct? What am I missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I completely missed your change representation.cc::368 to 380. Sorry about that. Yes, I think that works. How does the generated mpd looks like with this change using your testing live stream? Are both audio and video representations the same as expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we were too conservative when we check if the timestamp is equal: https://github.com/google/shaka-packager/blob/master/packager/mpd/base/representation.cc#L383.
I think we can increase the error threshold. Can you try what happens if we increase the error threshold to 0.1 in https://github.com/google/shaka-packager/blob/master/packager/mpd/base/representation.cc#L395?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kqyang Thanks. Looks like the issue was with the source file. After adding -async 1 option while creating mpeg2ts from mp4, I am get the following mpd:
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version ce932f68a2-debug-->
<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="2020-04-18T02:47:11Z" availabilityStartTime="
2020-04-18T01:48:38Z" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT50S">
<Period id="0" start="PT0S">
<AdaptationSet id="0" contentType="audio" segmentAlignment="true">
<Representation id="0" bandwidth="130986" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="48000">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<SegmentTemplate timescale="90000" initialization="live-audio.mp4" media="live-audio-$Number$.mp4" startNumber="347" duration="900000"/>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="video" width="1920" height="1080" frameRate="90000/3750" segmentAlignment="true" par="16:9">
<Representation id="1" bandwidth="2990276" codecs="avc1.4d401f" mimeType="video/mp4" sar="1:1">
<SegmentTemplate timescale="90000" initialization="live-video.mp4" media="live-video-$Number$.mp4" startNumber="347" duration="900000"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>
Tired increasing the error threshold but the video frame duration is 3750, audio frame duration is 1920 and time scale is 90000. As
std::min(frame_duration_,
static_cast<uint32_t>(kErrorThresholdSeconds *
media_info_.reference_time_scale()));
looks like frame duration will always be selected as error threshold.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the issue was with the source file. After adding -async 1 option while creating mpeg2ts from mp4, I am get the following mpd:
Great! This looks like exactly what we want.
looks like frame duration will always be selected as error threshold.
If needed, I am fine with removing the frame duration from the calculation.
Back to the original comment, I am still not very comfortable with depending on an additional parameter in IsTimelineConstantDuration.
Also, with the shorter segment removed, it isn't really necessary to have the additional parameter.
We can keep the original code other than changing
if (first_segment.start_time != first_segment.duration * (start_number - 1))
to
if (first_segment.start_time / first_segment.duration != * (start_number - 1))
It may return false for the very first two segments, but that is fine, as it is true that the very first segment is not really compliant to the constant duration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kqyang I have made the changes as per above comments.
If needed, I am fine with removing the frame duration from the calculation.
Sounds good. We can remove it if the problem still persists. What can be the limit of the error threshold? Will it be +-50% as per DASH IOP "Segments should have almost equal duration. The maximum tolerance of segment duration shall be ±50% and the maximum accumulated deviation over multiple segments shall be ±50% of the signaled segment duration (i.e. the @duration). "? Also, should the player rely on sidx/traf/tdft box information to tolerate the segment duration variation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at sidx boxes in the segments generated, the subsegment_duration look consistent for both video and audio and within error threshold but sometimes seem to go way over the error threshold for audio (example: 935040).
Video subsegment duration:
live-video-100.mp4
entry 0000 = reference_type=0, referenced_size=2827855, subsegment_duration=900007, starts_with_SAP=1, SAP_type=1, SAP_delta_time=0
live-video-101.mp4
entry 0000 = reference_type=0, referenced_size=2997414, subsegment_duration=900007, starts_with_SAP=1, SAP_type=1, SAP_delta_time=0
live-video-102.mp4
entry 0000 = reference_type=0, referenced_size=2714349, subsegment_duration=900007, starts_with_SAP=1, SAP_type=1, SAP_delta_time=0
live-video-103.mp4
entry 0000 = reference_type=0, referenced_size=2573218, subsegment_duration=900008, starts_with_SAP=1, SAP_type=1, SAP_delta_time=0
live-video-104.mp4
Audio subsegement_duration:
entry 0000 = reference_type=0, referenced_size=163055, subsegment_duration=900480, starts_with_SAP=1, SAP_type=1, SAP_delta_time=0
live-audio-101.mp4
entry 0000 = reference_type=0, referenced_size=162631, subsegment_duration=898560, starts_with_SAP=1, SAP_type=1, SAP_delta_time=0
live-audio-102.mp4
entry 0000 = reference_type=0, referenced_size=162238, subsegment_duration=900480, starts_with_SAP=1, SAP_type=1, SAP_delta_time=0
live-audio-103.mp4
entry 0000 = reference_type=0, referenced_size=163030, subsegment_duration=900480, starts_with_SAP=1, SAP_type=1, SAP_delta_time=0
live-audio-104.mp4
On player side,
Exoplayer sidx parser calculates the duration based on segment_duration:
https://github.com/google/ExoPlayer/blob/7d3f54a375fac04a746ca76a8f2e1ad32c8b45b2/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java#L1155
Segment Duration is retrieved at:
https://github.com/google/ExoPlayer/blob/7d3f54a375fac04a746ca76a8f2e1ad32c8b45b2/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java#L183
while loading media chunks at the given renderer position and in our case it should rely on :
((duration * C.MICROS_PER_SECOND) / timescale).
@kqyang Is my understanding correct or am I missing anything? Thanks.
98ecdab
to
c7283e7
Compare
packager/mpd/base/xml/xml_node.cc
Outdated
@@ -47,7 +47,8 @@ std::string RangeToString(const Range& range) { | |||
// Check if segments are continuous and all segments except the last one are of | |||
// the same duration. | |||
bool IsTimelineConstantDuration(const std::list<SegmentInfo>& segment_infos, | |||
uint32_t start_number) { | |||
uint32_t start_number, | |||
const double target_duration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need to pass |target_duration| to this function with the new code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have made the above change. Sorry about that.
packager/mpd/base/xml/xml_node.cc
Outdated
if (IsTimelineConstantDuration( | ||
segment_infos, start_number, | ||
target_duration * media_info.reference_time_scale())) { | ||
if (target_duration > 0) { | ||
segment_template.SetIntegerAttribute( | ||
"duration", target_duration * media_info.reference_time_scale()); | ||
} else { | ||
segment_template.SetIntegerAttribute("duration", | ||
segment_infos.front().duration); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly here. The code does not need to change.
The same below.
c7283e7
to
4ad6161
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sr1990 Good job!
Now the second part of the problem, which is also more challenging than the first part: we'll need to make sure the file name matches with the new |start_number|.
Here is a possible approach:
-
Define a new member in SegmentInfo: int64_t segment_index.
-
Fill in
segment_index
in ChunkingHandler -
Similarly for TextChunker
-
Update MuxerListener::OnNewSegment to include a new parameter for segment index.
-
In MP4 multi-segmenter, use the segment_index from SegmentInfo to generate the file name: https://github.com/google/shaka-packager/blob/6b4a75b6bacdac6b0b9f598c365c8f84f0a7fd70/packager/media/formats/mp4/multi_segment_segmenter.cc#L164. Update https://github.com/google/shaka-packager/blob/6b4a75b6bacdac6b0b9f598c365c8f84f0a7fd70/packager/media/formats/mp4/multi_segment_segmenter.cc#L212 accordingly.
-
Similarly for WebM, mpeg2ts and other formats.
-
Update SimpleMpdNotifier::NotifyNewSegment with a new segment_index parameter. Similarly for Representation class.
There could be clean-ups here and there.
@sr1990 Let me know what you think! Thanks again for working on this feature.
@@ -799,12 +799,16 @@ TEST_P(ApproximateSegmentTimelineTest, | |||
if (allow_approximate_segment_timeline_) { | |||
expected_s_elements = base::StringPrintf( | |||
kSElementTemplateWithoutR, kStartTime, kScaledTargetSegmentDuration); | |||
EXPECT_THAT(representation_->GetXml().get(), | |||
XmlNodeEqual(SegmentTimelineTestBase::ExpectedXml( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is "SegmentTimelineTestBase::" necessary here? Same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kqyang It's a name hiding issue. So, I can either do the above or add
using SegmentTimelineTestBase::ExpectedXml;
in ApproximateSegmentTimelineTest class. Let me know what you think would be right here.
@kqyang Thanks for the review. I am trying out the approach that you have mentioned with the above stream and able to create segment names with correct segment index. I am looking into changes to other classes related to this and will update the PR after making those changes. |
Cool. Thanks. |
4ad6161
to
0f703bf
Compare
@kqyang I have updated the PR. In cases where FinalizeSegment does not create the segment file (ts_segmenter and webm's multisegment segmenter), the file that is created gets renamed with the correct segment index in it . Other than that the code follows the steps that you have mentioned above. Please let me know what you think. |
Hey @kqyang Any thoughts about this update? and review? |
@sr1990 Sorry, was a little bit busy recently. I'll take a look ASAP. |
Great. Thanks. |
@kqyang ping. |
Sorry for the delay. Reviewing right now. Will try to complete it this week. |
Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the hard work. Sorry for the late review.
packager/file/udp_file.cc
Outdated
@@ -27,7 +27,7 @@ | |||
// IP_MULTICAST_ALL has been supported since kernel version 2.6.31 but we may be | |||
// building on a machine that is older than that. | |||
#ifndef IP_MULTICAST_ALL | |||
#define IP_MULTICAST_ALL 49 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind moving style changes to a separate CL? It will make it easier for code review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have reverted back most of style changes. Will try to revert more changes.
// RENAME FILE: | ||
std::string segment_name_segment_index = | ||
GetSegmentName(muxer_options_.segment_template, next_pts_, | ||
segment_index - 1, muxer_options_.bandwidth); | ||
|
||
if (!ts_writer_->RenameFile(segment_name_segment_index)) { | ||
LOG(ERROR) << "TS Error renaming the file"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, renaming the file after the segment is completed won't work for "Low Latency Live Streaming" we want to achieve later.
We have two options:
(1) Store the segment data in a buffer first, and write it to the file only after the segment is completed.
- We could either store the data in a list if MediaSamples or
- Write the samples to a uint8_t byte buffer.
(2) Add a new kSegmentInfoStart stream data type and send it when the segment begins in ChunkingHandler
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kqyang, thanks for the review. For option (1), how about using BufferWriter as a member in Writer classes to store the bytes to be written in file and then in FinalizeSegment, this buffer can be written to the file? Does that make sense for both mpegts (write to buffer instead of file in WritePesToFile) and webm (MkvWriter::Write and MkvWriter::WriteFromFile)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kqyang Have updated the PR by reverting the file renaming and used buffer for mp2ts and webm. Please review and let me know.
@@ -72,7 +72,7 @@ class ChunkingHandler : public MediaHandler { | |||
// Segment and subsegment duration in stream's time scale. | |||
int64_t segment_duration_ = 0; | |||
int64_t subsegment_duration_ = 0; | |||
|
|||
int64_t set_segment_index_ = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is already a variable for segment index: current_segment_index_. Why do we need another one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
current_segment_index_ is used to compare with segment index calculated at line: https://github.com/google/shaka-packager/blob/8913dbda85967b26e4968495b1d0ed70fbffe1fd/packager/media/chunking/chunking_handler.cc#L99 and
https://github.com/google/shaka-packager/blob/8913dbda85967b26e4968495b1d0ed70fbffe1fd/packager/media/chunking/chunking_handler.cc#L103
whereas
set_segment_index_ is used for keeping the segment count for file creation and needs to be incremented in onCueEvent and ChunkingHandler::OnFlushRequest (which also call EndSegmentIfStarted()). If the current_segment_index_ is used here instead of set_segment_index_, the result of comparison in https://github.com/google/shaka-packager/blob/8913dbda85967b26e4968495b1d0ed70fbffe1fd/packager/media/chunking/chunking_handler.cc#L103 will change.
Please let me know what you think.
Also I noticed that I made a mistake with isGreaterSegmentIndex method. It should use set_segment_index_ instead of current_segment_index_ (as set_segment_index_ should never be decremented). I will make this change if you think the above explanation is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I am afraid that won't work though as the increment is undone by the code from line 109 to 113.
Since the cue basically resets segment index back to 0 [1], I think we can keep a record of segment count: num_segments_before_last_cue_
.
In OnCueEvent
function:
Status ChunkingHandler::OnCueEvent(std::shared_ptr<const CueEvent> event) {
...
num_segments_before_last_cue_ = current_segment_index_ + 1;
return Status::OK;
}
In EndSegmentIfStarted
function:
Status ChunkingHandler::EndSegmentIfStarted() const {
...
segment_info->segment_index = current_segment_index_ + num_segments_before_last_cue_;
return DispatchSegmentInfo(kStreamIndex, std::move(segment_info));
}
[1] See the code at line 106: timestamp - cue_offset_
ChunkingHandler::OnFlushRequest
does not need to be updated as it is actually called only once in the end.
275ae6e
to
f522c01
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. This is a partial review as I haven't looked at your changes on WebM and mp4.
I like your change on TS writer! Left a few comments, which probably applies to WebM and mp4 too.
Would you mind breaking this PR into multiple smaller PRs? Sorry I should have talked to you about it earlier as it is really difficult to review a gigantic PR. It would also take a long time to finish.
For example, one of the PR could be TS writer / TS segmenter.
@@ -249,6 +249,7 @@ std::unique_ptr<SegmentInfo> MediaHandlerTestBase::GetSegmentInfo( | |||
info->start_timestamp = start_timestamp; | |||
info->duration = duration; | |||
info->is_subsegment = is_subsegment; | |||
info->segment_index = start_timestamp / duration + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is there a "+1" in the end? Shouldn't segment_index
be 0 based?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. This is a partial review as I haven't looked at your changes on WebM and mp4.
I like your change on TS writer! Left a few comments, which probably applies to WebM and mp4 too.
Would you mind breaking this PR into multiple smaller PRs? Sorry I should have talked to you about it earlier as it is really difficult to review a gigantic PR. It would also take a long time to finish.
For example, one of the PR could be TS writer / TS segmenter.
@kqyang
Makes sense. I will divide the pr into
- ChunkHandler - for sending segment index
- TS writer/ TS segmenter
- Webm
- MP4
- WebVTT + rest
How does that sound?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kqyang Have created 2 PRs for WebM and MPEG2TS changes. Will create a PR for sending segment index and handling when these two are merged. Please review and let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great. Thanks a lot for doing that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kqyang Have created 1 PR that sends segment index from (chunking_handler, text_chunker) -> muxer -> segmenters to generate segment with segment name based on segment index -> MpdNotifyMuxerListener -> SimpleMpdNotifier -> Representation which sets the start number based on segment index that it receives.
Please review and let me know. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sr1990 Thanks. Will take a look in the next few days.
@@ -72,7 +72,7 @@ class ChunkingHandler : public MediaHandler { | |||
// Segment and subsegment duration in stream's time scale. | |||
int64_t segment_duration_ = 0; | |||
int64_t subsegment_duration_ = 0; | |||
|
|||
int64_t set_segment_index_ = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I am afraid that won't work though as the increment is undone by the code from line 109 to 113.
Since the cue basically resets segment index back to 0 [1], I think we can keep a record of segment count: num_segments_before_last_cue_
.
In OnCueEvent
function:
Status ChunkingHandler::OnCueEvent(std::shared_ptr<const CueEvent> event) {
...
num_segments_before_last_cue_ = current_segment_index_ + 1;
return Status::OK;
}
In EndSegmentIfStarted
function:
Status ChunkingHandler::EndSegmentIfStarted() const {
...
segment_info->segment_index = current_segment_index_ + num_segments_before_last_cue_;
return DispatchSegmentInfo(kStreamIndex, std::move(segment_info));
}
[1] See the code at line 106: timestamp - cue_offset_
ChunkingHandler::OnFlushRequest
does not need to be updated as it is actually called only once in the end.
@@ -345,7 +345,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) { | |||
kSegmentDuration, _, kSegmentSize)); | |||
listener_.OnCueEvent(kCueStartTime, "dummy cue data"); | |||
listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, | |||
kSegmentDuration, kSegmentSize); | |||
kSegmentDuration, kSegmentSize, 10); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer using a named constant to make it easier to readers. For example, can we use kSegmentIndex? Same below
if (mpd_notifier_->dash_profile() == DashProfile::kLive) { | ||
mpd_notifier_->NotifyNewSegment(notification_id_.value(), start_time, | ||
duration, segment_file_size); | ||
duration, segment_file_size, segment_index); | ||
if (mpd_notifier_->mpd_type() == MpdType::kDynamic) | ||
mpd_notifier_->Flush(); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should save segment_index
in event_info.segment_info
below to avoid calculating segment_index
in line 156 - 159 above.
if (ts_writer_buffer_initialized_) { | ||
std::string segment_name_segment_index = | ||
GetSegmentName(muxer_options_.segment_template, next_pts_, | ||
segment_index - 1, muxer_options_.bandwidth); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be segment_index
instead of segment_index - 1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sending segment_index-1 as GetSegmentName increments the segment index by 1 to create file name. Will modify the code so that segment index that is sent is 0 based.
GetSegmentName(muxer_options_.segment_template, next_pts, | ||
segment_number_++, muxer_options_.bandwidth); | ||
if (!ts_writer_->NewSegment(segment_name)) | ||
next_pts_ = next_pts; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call it segment_start_timestamp_
?
@@ -70,6 +73,8 @@ class TsSegmenter { | |||
/// Only for testing. | |||
void SetTsWriterFileOpenedForTesting(bool value); | |||
|
|||
int next_pts_ = -1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Class members should be private
instead of protected
.
GetSegmentName(muxer_options_.segment_template, next_pts_, | ||
segment_index - 1, muxer_options_.bandwidth); | ||
|
||
current_segment_path_ = segment_name_segment_index; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this variable still need to be a class member?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this variable still need to be a class member?
not needed. Will remove it.
// be false. | ||
if (ts_writer_file_opened_) { | ||
if (ts_writer_buffer_initialized_) { | ||
std::string segment_name_segment_index = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just call it segment_name
or segment_path
.
segment_index - 1, muxer_options_.bandwidth); | ||
|
||
current_segment_path_ = segment_name_segment_index; | ||
if (!ts_writer_->CreateFileAndFlushBuffer(segment_name_segment_index)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call the function: WriteToFile
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
bump |
Bump |
This needs to be rebased on top of main, and really on top of the changes from #879 as well. |
@sr1990 do you want to take another stab at this on top of current main? |
…ine option
Creating a PR as per discussion at #554