Fix WebM timestamp overflow if longer than 2.5 hours

Fixes #233

Change-Id: I3431f8e68bfb1de222b8ab967c9cac6a821b4f53
This commit is contained in:
KongQun Yang 2017-05-12 17:02:12 -07:00
parent 27abb208aa
commit 3443048e80
12 changed files with 233 additions and 98 deletions

View File

@ -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));

View File

@ -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

View File

@ -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<MkvWriter> writer_;
uint32_t num_segment_;

View File

@ -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"));
}

View File

@ -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<uint64_t>(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<double>(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<double>(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<MediaSample> 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<float>(segment_info_.duration()) *
segment_info_.timecode_scale() / kSecondsToNs;
float Segmenter::GetDurationInSeconds() const {
return WebMTimecodeToNs(segment_info_.duration(),
segment_info_.timecode_scale()) /
static_cast<double>(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<double>(frame.timestamp()) / kSecondsToNs;
LOG(ERROR) << "Error adding sample to segment: segment too large, "

View File

@ -52,8 +52,8 @@ class Segmenter {
Status AddSample(std::shared_ptr<MediaSample> 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<MediaSample> prev_sample_;

View File

@ -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<MediaSample> 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<int>(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;

View File

@ -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<int> cluster_sizes_;
bool in_cluster_;
int64_t cluster_timecode_ = -1;
// frame_timecodes_[cluster_index][frame_index].
std::vector<std::vector<int64_t>> 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_;
};

View File

@ -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

View File

@ -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<MkvWriter> writer_;
uint64_t init_end_;

View File

@ -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<MediaSample> 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<MediaSample> 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

View File

@ -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());