diff --git a/packager/media/formats/webm/encrypted_segmenter_unittest.cc b/packager/media/formats/webm/encrypted_segmenter_unittest.cc index 51eb9f1a43..02c65df08d 100644 --- a/packager/media/formats/webm/encrypted_segmenter_unittest.cc +++ b/packager/media/formats/webm/encrypted_segmenter_unittest.cc @@ -12,7 +12,8 @@ namespace shaka { namespace media { namespace { -const uint64_t kDuration = 1000u; +const uint32_t kTimeScale = 1000000u; +const uint64_t kDuration = 1000000u; const bool kSubsegment = true; const uint8_t kPerSampleIvSize = 8u; const uint8_t kKeyId[] = { @@ -196,7 +197,7 @@ const uint8_t kBasicSupportData[] = { class EncryptedSegmenterTest : public SegmentTestBase { public: - EncryptedSegmenterTest() : info_(CreateVideoStreamInfo()) { + EncryptedSegmenterTest() : info_(CreateVideoStreamInfo(kTimeScale)) { EncryptionConfig encryption_config; encryption_config.per_sample_iv_size = kPerSampleIvSize; encryption_config.key_id.assign(kKeyId, kKeyId + sizeof(kKeyId)); diff --git a/packager/media/formats/webm/multi_segment_segmenter.cc b/packager/media/formats/webm/multi_segment_segmenter.cc index 6b93e22510..094e497fba 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.cc +++ b/packager/media/formats/webm/multi_segment_segmenter.cc @@ -21,12 +21,12 @@ MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options) MultiSegmentSegmenter::~MultiSegmentSegmenter() {} -Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timescale, - uint64_t duration_timescale, +Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp, + uint64_t duration_timestamp, bool is_subsegment) { CHECK(cluster()); - Status status = Segmenter::FinalizeSegment(start_timescale, - duration_timescale, is_subsegment); + Status status = Segmenter::FinalizeSegment(start_timestamp, + duration_timestamp, is_subsegment); if (!status.ok()) return status; if (!cluster()->Finalize()) @@ -35,7 +35,7 @@ Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timescale, if (muxer_listener()) { const uint64_t size = cluster()->Size(); muxer_listener()->OnNewSegment(writer_->file()->file_name(), - start_timescale, duration_timescale, size); + start_timestamp, duration_timestamp, size); } VLOG(1) << "WEBM file '" << writer_->file()->file_name() << "' finalized."; } @@ -65,12 +65,12 @@ Status MultiSegmentSegmenter::DoFinalize() { return writer_->Close(); } -Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale, +Status MultiSegmentSegmenter::NewSegment(uint64_t start_timestamp, bool is_subsegment) { if (!is_subsegment) { // Create a new file for the new segment. std::string segment_name = - GetSegmentName(options().segment_template, start_timescale, + GetSegmentName(options().segment_template, start_timestamp, num_segment_, options().bandwidth); writer_.reset(new MkvWriter); Status status = writer_->Open(segment_name); @@ -79,8 +79,8 @@ Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale, num_segment_++; } - uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale); - return SetCluster(start_webm_timecode, 0, writer_.get()); + const uint64_t start_timecode = FromBmffTimestamp(start_timestamp); + return SetCluster(start_timecode, 0, writer_.get()); } } // namespace webm diff --git a/packager/media/formats/webm/multi_segment_segmenter.h b/packager/media/formats/webm/multi_segment_segmenter.h index 5819d854af..0a7976cc88 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.h +++ b/packager/media/formats/webm/multi_segment_segmenter.h @@ -28,8 +28,8 @@ class MultiSegmentSegmenter : public Segmenter { /// @name Segmenter implementation overrides. /// @{ - Status FinalizeSegment(uint64_t start_timescale, - uint64_t duration_timescale, + Status FinalizeSegment(uint64_t start_timestamp, + uint64_t duration_timestamp, bool is_subsegment) override; bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; @@ -42,7 +42,7 @@ class MultiSegmentSegmenter : public Segmenter { private: // Segmenter implementation overrides. - Status NewSegment(uint64_t start_timescale, bool is_subsegment) override; + Status NewSegment(uint64_t start_timestamp, bool is_subsegment) override; std::unique_ptr writer_; uint32_t num_segment_; diff --git a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc index c04094c680..2cde896ca5 100644 --- a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc @@ -13,7 +13,8 @@ namespace shaka { namespace media { namespace { -const uint64_t kDuration = 1000; +const uint32_t kTimeScale = 1000000u; +const uint64_t kDuration = 1000000u; const bool kSubsegment = true; const uint8_t kBasicSupportDataInit[] = { @@ -94,7 +95,7 @@ const uint8_t kBasicSupportDataSegment[] = { class MultiSegmentSegmenterTest : public SegmentTestBase { public: MultiSegmentSegmenterTest() - : info_(CreateVideoStreamInfo()), + : info_(CreateVideoStreamInfo(kTimeScale)), segment_template_(std::string(kMemoryFilePrefix) + "output-template-$Number$.webm") {} @@ -157,11 +158,11 @@ TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) { ClusterParser parser; ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); ASSERT_EQ(1u, parser.cluster_count()); - EXPECT_EQ(5, parser.GetFrameCountForCluster(0)); + EXPECT_EQ(5u, parser.GetFrameCountForCluster(0)); ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1))); ASSERT_EQ(1u, parser.cluster_count()); - EXPECT_EQ(3, parser.GetFrameCountForCluster(0)); + EXPECT_EQ(3u, parser.GetFrameCountForCluster(0)); EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r")); } @@ -186,8 +187,8 @@ TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) { ClusterParser parser; ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); ASSERT_EQ(2u, parser.cluster_count()); - EXPECT_EQ(5, parser.GetFrameCountForCluster(0)); - EXPECT_EQ(3, parser.GetFrameCountForCluster(1)); + EXPECT_EQ(5u, parser.GetFrameCountForCluster(0)); + EXPECT_EQ(3u, parser.GetFrameCountForCluster(1)); EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r")); } diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index f04de8db8f..2e2cffa8c5 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -30,8 +30,45 @@ namespace shaka { namespace media { namespace webm { namespace { -int64_t kTimecodeScale = 1000000; -int64_t kSecondsToNs = 1000000000L; +const int64_t kTimecodeScale = 1000000; +const int64_t kSecondsToNs = 1000000000L; + +// Round to closest integer. +uint64_t Round(double value) { + return static_cast(value + 0.5); +} + +// There are three different kinds of timestamp here: +// (1) ISO-BMFF timestamp (seconds scaled by ISO-BMFF timescale) +// This is used in our MediaSample and StreamInfo structures. +// (2) WebM timecode (seconds scaled by kSecondsToNs / WebM timecode scale) +// This is used in most WebM structures. +// (3) Nanoseconds (seconds scaled by kSecondsToNs) +// This is used in some WebM structures, e.g. Frame. +// We use Nanoseconds as intermediate format here for conversion, in +// uint64_t/int64_t, which is sufficient to represent a time as large as 292 +// years. + +uint64_t BmffTimestampToNs(uint64_t timestamp, uint64_t time_scale) { + // Casting to double is needed otherwise kSecondsToNs * timestamp may overflow + // uint64_t/int64_t. + return Round(static_cast(timestamp) / time_scale * kSecondsToNs); +} + +uint64_t NsToBmffTimestamp(uint64_t ns, uint64_t time_scale) { + // Casting to double is needed otherwise ns * time_scale may overflow + // uint64_t/int64_t. + return Round(static_cast(ns) / kSecondsToNs * time_scale); +} + +uint64_t NsToWebMTimecode(uint64_t ns, uint64_t timecode_scale) { + return ns / timecode_scale; +} + +uint64_t WebMTimecodeToNs(uint64_t timecode, uint64_t timecode_scale) { + return timecode * timecode_scale; +} + } // namespace Segmenter::Segmenter(const MuxerOptions& options) : options_(options) {} @@ -112,7 +149,7 @@ Status Segmenter::Initialize(StreamInfo* info, Status Segmenter::Finalize() { uint64_t duration = prev_sample_->pts() - first_timestamp_ + prev_sample_->duration(); - segment_info_.set_duration(FromBMFFTimescale(duration)); + segment_info_.set_duration(FromBmffTimestamp(duration)); return DoFinalize(); } @@ -150,8 +187,8 @@ Status Segmenter::AddSample(std::shared_ptr sample) { return Status::OK; } -Status Segmenter::FinalizeSegment(uint64_t start_timescale, - uint64_t duration_timescale, +Status Segmenter::FinalizeSegment(uint64_t start_timestamp, + uint64_t duration_timestamp, bool is_subsegment) { if (is_subsegment) new_subsegment_ = true; @@ -160,22 +197,22 @@ Status Segmenter::FinalizeSegment(uint64_t start_timescale, return WriteFrame(true /* write duration */); } -float Segmenter::GetDuration() const { - return static_cast(segment_info_.duration()) * - segment_info_.timecode_scale() / kSecondsToNs; +float Segmenter::GetDurationInSeconds() const { + return WebMTimecodeToNs(segment_info_.duration(), + segment_info_.timecode_scale()) / + static_cast(kSecondsToNs); } -uint64_t Segmenter::FromBMFFTimescale(uint64_t time_timescale) { - // Convert the time from BMFF time_code to WebM timecode scale. - const int64_t time_ns = - kSecondsToNs * time_timescale / info_->time_scale(); - return time_ns / segment_info_.timecode_scale(); +uint64_t Segmenter::FromBmffTimestamp(uint64_t bmff_timestamp) { + return NsToWebMTimecode( + BmffTimestampToNs(bmff_timestamp, info_->time_scale()), + segment_info_.timecode_scale()); } -uint64_t Segmenter::FromWebMTimecode(uint64_t time_webm_timecode) { - // Convert the time to BMFF time_code from WebM timecode scale. - const int64_t time_ns = time_webm_timecode * segment_info_.timecode_scale(); - return time_ns * info_->time_scale() / kSecondsToNs; +uint64_t Segmenter::FromWebMTimecode(uint64_t webm_timecode) { + return NsToBmffTimestamp( + WebMTimecodeToNs(webm_timecode, segment_info_.timecode_scale()), + info_->time_scale()); } Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) { @@ -334,12 +371,12 @@ Status Segmenter::WriteFrame(bool write_duration) { } if (write_duration) { - const uint64_t duration_ns = - prev_sample_->duration() * kSecondsToNs / info_->time_scale(); - frame.set_duration(duration_ns); + frame.set_duration( + BmffTimestampToNs(prev_sample_->duration(), info_->time_scale())); } frame.set_is_key(prev_sample_->is_key_frame()); - frame.set_timestamp(prev_sample_->pts() * kSecondsToNs / info_->time_scale()); + frame.set_timestamp( + BmffTimestampToNs(prev_sample_->pts(), info_->time_scale())); frame.set_track_number(track_id_); if (prev_sample_->side_data_size() > 0) { @@ -359,15 +396,14 @@ Status Segmenter::WriteFrame(bool write_duration) { } if (!prev_sample_->is_key_frame() && !frame.CanBeSimpleBlock()) { - const int64_t timestamp_ns = - reference_frame_timestamp_ * kSecondsToNs / info_->time_scale(); - frame.set_reference_block_timestamp(timestamp_ns); + frame.set_reference_block_timestamp( + BmffTimestampToNs(reference_frame_timestamp_, info_->time_scale())); } // GetRelativeTimecode will return -1 if the relative timecode is too large // to fit in the frame. - if (cluster_->GetRelativeTimecode(frame.timestamp() / - cluster_->timecode_scale()) < 0) { + if (cluster_->GetRelativeTimecode(NsToWebMTimecode( + frame.timestamp(), cluster_->timecode_scale())) < 0) { const double segment_duration = static_cast(frame.timestamp()) / kSecondsToNs; LOG(ERROR) << "Error adding sample to segment: segment too large, " diff --git a/packager/media/formats/webm/segmenter.h b/packager/media/formats/webm/segmenter.h index be63eb0647..0c73877870 100644 --- a/packager/media/formats/webm/segmenter.h +++ b/packager/media/formats/webm/segmenter.h @@ -52,8 +52,8 @@ class Segmenter { Status AddSample(std::shared_ptr sample); /// Finalize the (sub)segment. - virtual Status FinalizeSegment(uint64_t start_timescale, - uint64_t duration_timescale, + virtual Status FinalizeSegment(uint64_t start_timestamp, + uint64_t duration_timestamp, bool is_subsegment) = 0; /// @return true if there is an initialization range, while setting @a start @@ -65,14 +65,13 @@ class Segmenter { virtual bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) = 0; /// @return The total length, in seconds, of segmented media files. - float GetDuration() const; + float GetDurationInSeconds() const; protected: - /// Converts the given time in ISO BMFF timescale to the current WebM - /// timecode. - uint64_t FromBMFFTimescale(uint64_t time_timescale); - /// Converts the given time in WebM timecode to ISO BMFF timescale. - uint64_t FromWebMTimecode(uint64_t time_webm_timecode); + /// Converts the given time in ISO BMFF timestamp to WebM timecode. + uint64_t FromBmffTimestamp(uint64_t bmff_timestamp); + /// Converts the given time in WebM timecode to ISO BMFF timestamp. + uint64_t FromWebMTimecode(uint64_t webm_timecode); /// Writes the Segment header to @a writer. Status WriteSegmentHeader(uint64_t file_size, MkvWriter* writer); /// Creates a Cluster object with the given parameters. @@ -110,7 +109,7 @@ class Segmenter { // In single-segment mode, a Cluster is a segment and there is no subsegment. // In multi-segment mode, a new file is a segment and the clusters in the file // are subsegments. - virtual Status NewSegment(uint64_t start_timescale, bool is_subsegment) = 0; + virtual Status NewSegment(uint64_t start_timestamp, bool is_subsegment) = 0; // Store the previous sample so we know which one is the last frame. std::shared_ptr prev_sample_; diff --git a/packager/media/formats/webm/segmenter_test_base.cc b/packager/media/formats/webm/segmenter_test_base.cc index 651f90ce72..fe3514b146 100644 --- a/packager/media/formats/webm/segmenter_test_base.cc +++ b/packager/media/formats/webm/segmenter_test_base.cc @@ -22,8 +22,7 @@ const uint8_t kTestMediaSampleSideData[] = { 0x73, 0x69, 0x64, 0x65, 0x00}; const int kTrackId = 1; -const uint32_t kTimeScale = 1000; -const uint64_t kDuration = 8000; +const uint64_t kDurationInSeconds = 8; const Codec kCodec = kCodecVP8; const std::string kCodecString = "vp8"; const std::string kLanguage = "en"; @@ -42,7 +41,7 @@ void SegmentTestBase::SetUp() { SetPackagerVersionForTesting("test"); output_file_name_ = std::string(kMemoryFilePrefix) + "output-file.webm"; - cur_time_timescale_ = 0; + cur_timestamp_ = 0; } void SegmentTestBase::TearDown() { @@ -64,11 +63,11 @@ std::shared_ptr SegmentTestBase::CreateSample( sample = MediaSample::CopyFrom(kTestMediaSampleData, sizeof(kTestMediaSampleData), is_key_frame); } - sample->set_dts(cur_time_timescale_); - sample->set_pts(cur_time_timescale_); + sample->set_dts(cur_timestamp_); + sample->set_pts(cur_timestamp_); sample->set_duration(duration); - cur_time_timescale_ += duration; + cur_timestamp_ += duration; return sample; } @@ -81,24 +80,26 @@ MuxerOptions SegmentTestBase::CreateMuxerOptions() const { return ret; } -VideoStreamInfo* SegmentTestBase::CreateVideoStreamInfo() const { +VideoStreamInfo* SegmentTestBase::CreateVideoStreamInfo( + uint32_t time_scale) const { return new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kCodec, H26xStreamFormat::kUnSpecified, - kCodecString, NULL, 0, kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayFactor, kNaluLengthSize, kLanguage, false); + kTrackId, time_scale, kDurationInSeconds * time_scale, kCodec, + H26xStreamFormat::kUnSpecified, kCodecString, NULL, 0, kWidth, kHeight, + kPixelWidth, kPixelHeight, kTrickPlayFactor, kNaluLengthSize, kLanguage, + false); } std::string SegmentTestBase::OutputFileName() const { return output_file_name_; } -SegmentTestBase::ClusterParser::ClusterParser() : in_cluster_(false) {} +SegmentTestBase::ClusterParser::ClusterParser() {} SegmentTestBase::ClusterParser::~ClusterParser() {} void SegmentTestBase::ClusterParser::PopulateFromCluster( const std::string& file_name) { - cluster_sizes_.clear(); + frame_timecodes_.clear(); std::string file_contents; ASSERT_TRUE(File::ReadFileToString(file_name.c_str(), &file_contents)); @@ -118,7 +119,7 @@ void SegmentTestBase::ClusterParser::PopulateFromCluster( void SegmentTestBase::ClusterParser::PopulateFromSegment( const std::string& file_name) { - cluster_sizes_.clear(); + frame_timecodes_.clear(); std::string file_contents; ASSERT_TRUE(File::ReadFileToString(file_name.c_str(), &file_contents)); @@ -133,13 +134,22 @@ void SegmentTestBase::ClusterParser::PopulateFromSegment( 0, segment_parser.Parse(data + offset, static_cast(size) - offset)); } -int SegmentTestBase::ClusterParser::GetFrameCountForCluster(size_t i) const { - DCHECK(i < cluster_sizes_.size()); - return cluster_sizes_[i]; +size_t SegmentTestBase::ClusterParser::GetFrameCountForCluster( + size_t cluster_index) const { + DCHECK_LT(cluster_index, frame_timecodes_.size()); + return frame_timecodes_[cluster_index].size(); +} + +int64_t SegmentTestBase::ClusterParser::GetFrameTimecode( + size_t cluster_index, + size_t frame_index) const { + DCHECK_LT(cluster_index, frame_timecodes_.size()); + DCHECK_LT(frame_index, frame_timecodes_[cluster_index].size()); + return frame_timecodes_[cluster_index][frame_index]; } size_t SegmentTestBase::ClusterParser::cluster_count() const { - return cluster_sizes_.size(); + return frame_timecodes_.size(); } WebMParserClient* SegmentTestBase::ClusterParser::OnListStart(int id) { @@ -147,7 +157,8 @@ WebMParserClient* SegmentTestBase::ClusterParser::OnListStart(int id) { if (in_cluster_) return NULL; - cluster_sizes_.push_back(0); + frame_timecodes_.emplace_back(); + cluster_timecode_ = -1; in_cluster_ = true; } @@ -165,6 +176,8 @@ bool SegmentTestBase::ClusterParser::OnListEnd(int id) { } bool SegmentTestBase::ClusterParser::OnUInt(int id, int64_t val) { + if (id == kWebMIdTimecode) + cluster_timecode_ = val; return true; } @@ -173,10 +186,15 @@ bool SegmentTestBase::ClusterParser::OnFloat(int id, double val) { } bool SegmentTestBase::ClusterParser::OnBinary(int id, - const uint8_t* data, - int size) { + const uint8_t* data, + int size) { if (in_cluster_ && (id == kWebMIdSimpleBlock || id == kWebMIdBlock)) { - cluster_sizes_.back()++; + if (cluster_timecode_ == -1) { + LOG(WARNING) << "Cluster timecode not yet available"; + return false; + } + int timecode = data[1] << 8 | data[2]; + frame_timecodes_.back().push_back(cluster_timecode_ + timecode); } return true; diff --git a/packager/media/formats/webm/segmenter_test_base.h b/packager/media/formats/webm/segmenter_test_base.h index 71fde6ad11..e77fc6637c 100644 --- a/packager/media/formats/webm/segmenter_test_base.h +++ b/packager/media/formats/webm/segmenter_test_base.h @@ -62,7 +62,7 @@ class SegmentTestBase : public ::testing::Test { /// Creates a Muxer options object for testing. MuxerOptions CreateMuxerOptions() const; /// Creates a video stream info object for testing. - VideoStreamInfo* CreateVideoStreamInfo() const; + VideoStreamInfo* CreateVideoStreamInfo(uint32_t time_scale) const; /// Gets the file name of the current output file. std::string OutputFileName() const; @@ -81,7 +81,8 @@ class SegmentTestBase : public ::testing::Test { void PopulateFromCluster(const std::string& file_name); void PopulateFromSegment(const std::string& file_name); - int GetFrameCountForCluster(size_t i) const; + size_t GetFrameCountForCluster(size_t cluster_index) const; + int64_t GetFrameTimecode(size_t cluster_index, size_t frame_index) const; size_t cluster_count() const; @@ -95,14 +96,18 @@ class SegmentTestBase : public ::testing::Test { bool OnString(int id, const std::string& str) override; private: - std::vector cluster_sizes_; - bool in_cluster_; + int64_t cluster_timecode_ = -1; + // frame_timecodes_[cluster_index][frame_index]. + std::vector> frame_timecodes_; + bool in_cluster_ = false; }; protected: + void set_cur_timestamp(uint64_t timestamp) { cur_timestamp_ = timestamp; } + std::string output_file_name_; std::string segment_template_; - uint64_t cur_time_timescale_; + uint64_t cur_timestamp_; bool single_segment_; }; diff --git a/packager/media/formats/webm/single_segment_segmenter.cc b/packager/media/formats/webm/single_segment_segmenter.cc index 95293f6f5a..6204ab0f88 100644 --- a/packager/media/formats/webm/single_segment_segmenter.cc +++ b/packager/media/formats/webm/single_segment_segmenter.cc @@ -18,11 +18,11 @@ SingleSegmentSegmenter::SingleSegmentSegmenter(const MuxerOptions& options) SingleSegmentSegmenter::~SingleSegmentSegmenter() {} -Status SingleSegmentSegmenter::FinalizeSegment(uint64_t start_timescale, - uint64_t duration_timescale, +Status SingleSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp, + uint64_t duration_timestamp, bool is_subsegment) { - Status status = Segmenter::FinalizeSegment(start_timescale, - duration_timescale, is_subsegment); + Status status = Segmenter::FinalizeSegment(start_timestamp, + duration_timestamp, is_subsegment); if (!status.ok()) return status; // No-op for subsegment in single segment mode. @@ -83,23 +83,23 @@ Status SingleSegmentSegmenter::DoFinalize() { return status; } -Status SingleSegmentSegmenter::NewSegment(uint64_t start_timescale, +Status SingleSegmentSegmenter::NewSegment(uint64_t start_timestamp, bool is_subsegment) { // No-op for subsegment in single segment mode. if (is_subsegment) return Status::OK; // Create a new Cue point. uint64_t position = writer_->Position(); - uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale); + uint64_t start_timecode = FromBmffTimestamp(start_timestamp); mkvmuxer::CuePoint* cue_point = new mkvmuxer::CuePoint; - cue_point->set_time(start_webm_timecode); + cue_point->set_time(start_timecode); cue_point->set_track(track_id()); cue_point->set_cluster_pos(position - segment_payload_pos()); if (!cues()->AddCue(cue_point)) return Status(error::INTERNAL_ERROR, "Error adding CuePoint."); - return SetCluster(start_webm_timecode, position, writer_.get()); + return SetCluster(start_timecode, position, writer_.get()); } } // namespace webm diff --git a/packager/media/formats/webm/single_segment_segmenter.h b/packager/media/formats/webm/single_segment_segmenter.h index cf044378bf..9707705e32 100644 --- a/packager/media/formats/webm/single_segment_segmenter.h +++ b/packager/media/formats/webm/single_segment_segmenter.h @@ -30,8 +30,8 @@ class SingleSegmentSegmenter : public Segmenter { /// @name Segmenter implementation overrides. /// @{ - Status FinalizeSegment(uint64_t start_timescale, - uint64_t duration_timescale, + Status FinalizeSegment(uint64_t start_timestamp, + uint64_t duration_timestamp, bool is_subsegment) override; bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; @@ -53,7 +53,7 @@ class SingleSegmentSegmenter : public Segmenter { private: // Segmenter implementation overrides. - Status NewSegment(uint64_t start_timescale, bool is_subsegment) override; + Status NewSegment(uint64_t start_timestamp, bool is_subsegment) override; std::unique_ptr writer_; uint64_t init_end_; diff --git a/packager/media/formats/webm/single_segment_segmenter_unittest.cc b/packager/media/formats/webm/single_segment_segmenter_unittest.cc index 544004d1d8..3ce2a4b19f 100644 --- a/packager/media/formats/webm/single_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/single_segment_segmenter_unittest.cc @@ -12,7 +12,10 @@ namespace shaka { namespace media { namespace { -const uint64_t kDuration = 1000; +const uint32_t kTimeScale = 1000000; +const uint32_t kTimecodeScale = 1000000; +const int64_t kSecondsToNs = 1000000000L; +const uint64_t kDuration = 1000000; const bool kSubsegment = true; const uint8_t kBasicSupportData[] = { @@ -135,7 +138,7 @@ const uint8_t kBasicSupportData[] = { class SingleSegmentSegmenterTest : public SegmentTestBase { public: - SingleSegmentSegmenterTest() : info_(CreateVideoStreamInfo()) {} + SingleSegmentSegmenterTest() : info_(CreateVideoStreamInfo(kTimeScale)) {} protected: void InitializeSegmenter(const MuxerOptions& options) { @@ -186,8 +189,8 @@ TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegment) { ClusterParser parser; ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName())); ASSERT_EQ(2u, parser.cluster_count()); - EXPECT_EQ(5, parser.GetFrameCountForCluster(0)); - EXPECT_EQ(3, parser.GetFrameCountForCluster(1)); + EXPECT_EQ(5u, parser.GetFrameCountForCluster(0)); + EXPECT_EQ(3u, parser.GetFrameCountForCluster(1)); } TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) { @@ -209,7 +212,79 @@ TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) { ClusterParser parser; ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName())); ASSERT_EQ(1u, parser.cluster_count()); - EXPECT_EQ(8, parser.GetFrameCountForCluster(0)); + EXPECT_EQ(8u, parser.GetFrameCountForCluster(0)); +} + +TEST_F(SingleSegmentSegmenterTest, LargeTimestamp) { + MuxerOptions options = CreateMuxerOptions(); + ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); + + // 3 hrs. It will overflow int64_t if multiplied by kSecondsToNs. + const int64_t kLargeTimestamp = 3ll * 3600 * kTimeScale; + set_cur_timestamp(kLargeTimestamp); + + // Write the samples to the Segmenter. + for (int i = 0; i < 5; i++) { + const SideDataFlag side_data_flag = + i == 3 ? kGenerateSideData : kNoSideData; + std::shared_ptr sample = + CreateSample(kKeyFrame, kDuration, side_data_flag); + ASSERT_OK(segmenter_->AddSample(sample)); + } + ASSERT_OK(segmenter_->FinalizeSegment(kLargeTimestamp, 5 * kDuration, + !kSubsegment)); + ASSERT_OK(segmenter_->Finalize()); + + // Verify the resulting data. + ClusterParser parser; + ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName())); + ASSERT_EQ(1u, parser.cluster_count()); + ASSERT_EQ(5u, parser.GetFrameCountForCluster(0)); + + const int64_t kClusterTimescode = + kLargeTimestamp / kTimeScale * kSecondsToNs / kTimecodeScale; + const int64_t kAdditionalTimecodePerSample = + kDuration / kTimeScale * kSecondsToNs / kTimecodeScale; + for (int i = 0; i < 5; i++) { + EXPECT_EQ(kClusterTimescode + kAdditionalTimecodePerSample * i, + parser.GetFrameTimecode(0, i)); + } +} + +TEST_F(SingleSegmentSegmenterTest, ReallyLargeTimestamp) { + MuxerOptions options = CreateMuxerOptions(); + ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); + + // 10 years. + const int64_t kReallyLargeTimestamp = 10ll * 365 * 24 * 3600 * kTimeScale; + set_cur_timestamp(kReallyLargeTimestamp); + + // Write the samples to the Segmenter. + for (int i = 0; i < 5; i++) { + const SideDataFlag side_data_flag = + i == 3 ? kGenerateSideData : kNoSideData; + std::shared_ptr sample = + CreateSample(kKeyFrame, kDuration, side_data_flag); + ASSERT_OK(segmenter_->AddSample(sample)); + } + ASSERT_OK(segmenter_->FinalizeSegment(kReallyLargeTimestamp, 5 * kDuration, + !kSubsegment)); + ASSERT_OK(segmenter_->Finalize()); + + // Verify the resulting data. + ClusterParser parser; + ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName())); + ASSERT_EQ(1u, parser.cluster_count()); + ASSERT_EQ(5u, parser.GetFrameCountForCluster(0)); + + const int64_t kClusterTimescode = + kReallyLargeTimestamp / kTimeScale * kSecondsToNs / kTimecodeScale; + const int64_t kAdditionalTimecodePerSample = + kDuration / kTimeScale * kSecondsToNs / kTimecodeScale; + for (int i = 0; i < 5; i++) { + EXPECT_EQ(kClusterTimescode + kAdditionalTimecodePerSample * i, + parser.GetFrameTimecode(0, i)); + } } } // namespace media diff --git a/packager/media/formats/webm/webm_muxer.cc b/packager/media/formats/webm/webm_muxer.cc index fa626cf0a6..80eba5834a 100644 --- a/packager/media/formats/webm/webm_muxer.cc +++ b/packager/media/formats/webm/webm_muxer.cc @@ -105,7 +105,7 @@ void WebMMuxer::FireOnMediaEndEvent() { const bool has_index_range = segmenter_->GetIndexRangeStartAndEnd( &index_range_start, &index_range_end); - const float duration_seconds = segmenter_->GetDuration(); + const float duration_seconds = segmenter_->GetDurationInSeconds(); const int64_t file_size = File::GetFileSize(options().output_file_name.c_str());