Fix WebM timestamp overflow if longer than 2.5 hours
Fixes #233 Change-Id: I3431f8e68bfb1de222b8ab967c9cac6a821b4f53
This commit is contained in:
parent
27abb208aa
commit
3443048e80
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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, "
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue