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 media {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const uint64_t kDuration = 1000u;
|
const uint32_t kTimeScale = 1000000u;
|
||||||
|
const uint64_t kDuration = 1000000u;
|
||||||
const bool kSubsegment = true;
|
const bool kSubsegment = true;
|
||||||
const uint8_t kPerSampleIvSize = 8u;
|
const uint8_t kPerSampleIvSize = 8u;
|
||||||
const uint8_t kKeyId[] = {
|
const uint8_t kKeyId[] = {
|
||||||
|
@ -196,7 +197,7 @@ const uint8_t kBasicSupportData[] = {
|
||||||
|
|
||||||
class EncryptedSegmenterTest : public SegmentTestBase {
|
class EncryptedSegmenterTest : public SegmentTestBase {
|
||||||
public:
|
public:
|
||||||
EncryptedSegmenterTest() : info_(CreateVideoStreamInfo()) {
|
EncryptedSegmenterTest() : info_(CreateVideoStreamInfo(kTimeScale)) {
|
||||||
EncryptionConfig encryption_config;
|
EncryptionConfig encryption_config;
|
||||||
encryption_config.per_sample_iv_size = kPerSampleIvSize;
|
encryption_config.per_sample_iv_size = kPerSampleIvSize;
|
||||||
encryption_config.key_id.assign(kKeyId, kKeyId + sizeof(kKeyId));
|
encryption_config.key_id.assign(kKeyId, kKeyId + sizeof(kKeyId));
|
||||||
|
|
|
@ -21,12 +21,12 @@ MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options)
|
||||||
|
|
||||||
MultiSegmentSegmenter::~MultiSegmentSegmenter() {}
|
MultiSegmentSegmenter::~MultiSegmentSegmenter() {}
|
||||||
|
|
||||||
Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timescale,
|
Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp,
|
||||||
uint64_t duration_timescale,
|
uint64_t duration_timestamp,
|
||||||
bool is_subsegment) {
|
bool is_subsegment) {
|
||||||
CHECK(cluster());
|
CHECK(cluster());
|
||||||
Status status = Segmenter::FinalizeSegment(start_timescale,
|
Status status = Segmenter::FinalizeSegment(start_timestamp,
|
||||||
duration_timescale, is_subsegment);
|
duration_timestamp, is_subsegment);
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
if (!cluster()->Finalize())
|
if (!cluster()->Finalize())
|
||||||
|
@ -35,7 +35,7 @@ Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timescale,
|
||||||
if (muxer_listener()) {
|
if (muxer_listener()) {
|
||||||
const uint64_t size = cluster()->Size();
|
const uint64_t size = cluster()->Size();
|
||||||
muxer_listener()->OnNewSegment(writer_->file()->file_name(),
|
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.";
|
VLOG(1) << "WEBM file '" << writer_->file()->file_name() << "' finalized.";
|
||||||
}
|
}
|
||||||
|
@ -65,12 +65,12 @@ Status MultiSegmentSegmenter::DoFinalize() {
|
||||||
return writer_->Close();
|
return writer_->Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale,
|
Status MultiSegmentSegmenter::NewSegment(uint64_t start_timestamp,
|
||||||
bool is_subsegment) {
|
bool is_subsegment) {
|
||||||
if (!is_subsegment) {
|
if (!is_subsegment) {
|
||||||
// Create a new file for the new segment.
|
// Create a new file for the new segment.
|
||||||
std::string segment_name =
|
std::string segment_name =
|
||||||
GetSegmentName(options().segment_template, start_timescale,
|
GetSegmentName(options().segment_template, start_timestamp,
|
||||||
num_segment_, options().bandwidth);
|
num_segment_, options().bandwidth);
|
||||||
writer_.reset(new MkvWriter);
|
writer_.reset(new MkvWriter);
|
||||||
Status status = writer_->Open(segment_name);
|
Status status = writer_->Open(segment_name);
|
||||||
|
@ -79,8 +79,8 @@ Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale,
|
||||||
num_segment_++;
|
num_segment_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale);
|
const uint64_t start_timecode = FromBmffTimestamp(start_timestamp);
|
||||||
return SetCluster(start_webm_timecode, 0, writer_.get());
|
return SetCluster(start_timecode, 0, writer_.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace webm
|
} // namespace webm
|
||||||
|
|
|
@ -28,8 +28,8 @@ class MultiSegmentSegmenter : public Segmenter {
|
||||||
|
|
||||||
/// @name Segmenter implementation overrides.
|
/// @name Segmenter implementation overrides.
|
||||||
/// @{
|
/// @{
|
||||||
Status FinalizeSegment(uint64_t start_timescale,
|
Status FinalizeSegment(uint64_t start_timestamp,
|
||||||
uint64_t duration_timescale,
|
uint64_t duration_timestamp,
|
||||||
bool is_subsegment) override;
|
bool is_subsegment) override;
|
||||||
bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
||||||
bool GetIndexRangeStartAndEnd(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:
|
private:
|
||||||
// Segmenter implementation overrides.
|
// 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_;
|
std::unique_ptr<MkvWriter> writer_;
|
||||||
uint32_t num_segment_;
|
uint32_t num_segment_;
|
||||||
|
|
|
@ -13,7 +13,8 @@ namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const uint64_t kDuration = 1000;
|
const uint32_t kTimeScale = 1000000u;
|
||||||
|
const uint64_t kDuration = 1000000u;
|
||||||
const bool kSubsegment = true;
|
const bool kSubsegment = true;
|
||||||
|
|
||||||
const uint8_t kBasicSupportDataInit[] = {
|
const uint8_t kBasicSupportDataInit[] = {
|
||||||
|
@ -94,7 +95,7 @@ const uint8_t kBasicSupportDataSegment[] = {
|
||||||
class MultiSegmentSegmenterTest : public SegmentTestBase {
|
class MultiSegmentSegmenterTest : public SegmentTestBase {
|
||||||
public:
|
public:
|
||||||
MultiSegmentSegmenterTest()
|
MultiSegmentSegmenterTest()
|
||||||
: info_(CreateVideoStreamInfo()),
|
: info_(CreateVideoStreamInfo(kTimeScale)),
|
||||||
segment_template_(std::string(kMemoryFilePrefix) +
|
segment_template_(std::string(kMemoryFilePrefix) +
|
||||||
"output-template-$Number$.webm") {}
|
"output-template-$Number$.webm") {}
|
||||||
|
|
||||||
|
@ -157,11 +158,11 @@ TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) {
|
||||||
ClusterParser parser;
|
ClusterParser parser;
|
||||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
||||||
ASSERT_EQ(1u, parser.cluster_count());
|
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_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1)));
|
||||||
ASSERT_EQ(1u, parser.cluster_count());
|
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"));
|
EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r"));
|
||||||
}
|
}
|
||||||
|
@ -186,8 +187,8 @@ TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) {
|
||||||
ClusterParser parser;
|
ClusterParser parser;
|
||||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
||||||
ASSERT_EQ(2u, parser.cluster_count());
|
ASSERT_EQ(2u, parser.cluster_count());
|
||||||
EXPECT_EQ(5, parser.GetFrameCountForCluster(0));
|
EXPECT_EQ(5u, parser.GetFrameCountForCluster(0));
|
||||||
EXPECT_EQ(3, parser.GetFrameCountForCluster(1));
|
EXPECT_EQ(3u, parser.GetFrameCountForCluster(1));
|
||||||
|
|
||||||
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,45 @@ namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace webm {
|
namespace webm {
|
||||||
namespace {
|
namespace {
|
||||||
int64_t kTimecodeScale = 1000000;
|
const int64_t kTimecodeScale = 1000000;
|
||||||
int64_t kSecondsToNs = 1000000000L;
|
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
|
} // namespace
|
||||||
|
|
||||||
Segmenter::Segmenter(const MuxerOptions& options) : options_(options) {}
|
Segmenter::Segmenter(const MuxerOptions& options) : options_(options) {}
|
||||||
|
@ -112,7 +149,7 @@ Status Segmenter::Initialize(StreamInfo* info,
|
||||||
Status Segmenter::Finalize() {
|
Status Segmenter::Finalize() {
|
||||||
uint64_t duration =
|
uint64_t duration =
|
||||||
prev_sample_->pts() - first_timestamp_ + prev_sample_->duration();
|
prev_sample_->pts() - first_timestamp_ + prev_sample_->duration();
|
||||||
segment_info_.set_duration(FromBMFFTimescale(duration));
|
segment_info_.set_duration(FromBmffTimestamp(duration));
|
||||||
return DoFinalize();
|
return DoFinalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,8 +187,8 @@ Status Segmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status Segmenter::FinalizeSegment(uint64_t start_timescale,
|
Status Segmenter::FinalizeSegment(uint64_t start_timestamp,
|
||||||
uint64_t duration_timescale,
|
uint64_t duration_timestamp,
|
||||||
bool is_subsegment) {
|
bool is_subsegment) {
|
||||||
if (is_subsegment)
|
if (is_subsegment)
|
||||||
new_subsegment_ = true;
|
new_subsegment_ = true;
|
||||||
|
@ -160,22 +197,22 @@ Status Segmenter::FinalizeSegment(uint64_t start_timescale,
|
||||||
return WriteFrame(true /* write duration */);
|
return WriteFrame(true /* write duration */);
|
||||||
}
|
}
|
||||||
|
|
||||||
float Segmenter::GetDuration() const {
|
float Segmenter::GetDurationInSeconds() const {
|
||||||
return static_cast<float>(segment_info_.duration()) *
|
return WebMTimecodeToNs(segment_info_.duration(),
|
||||||
segment_info_.timecode_scale() / kSecondsToNs;
|
segment_info_.timecode_scale()) /
|
||||||
|
static_cast<double>(kSecondsToNs);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Segmenter::FromBMFFTimescale(uint64_t time_timescale) {
|
uint64_t Segmenter::FromBmffTimestamp(uint64_t bmff_timestamp) {
|
||||||
// Convert the time from BMFF time_code to WebM timecode scale.
|
return NsToWebMTimecode(
|
||||||
const int64_t time_ns =
|
BmffTimestampToNs(bmff_timestamp, info_->time_scale()),
|
||||||
kSecondsToNs * time_timescale / info_->time_scale();
|
segment_info_.timecode_scale());
|
||||||
return time_ns / segment_info_.timecode_scale();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Segmenter::FromWebMTimecode(uint64_t time_webm_timecode) {
|
uint64_t Segmenter::FromWebMTimecode(uint64_t webm_timecode) {
|
||||||
// Convert the time to BMFF time_code from WebM timecode scale.
|
return NsToBmffTimestamp(
|
||||||
const int64_t time_ns = time_webm_timecode * segment_info_.timecode_scale();
|
WebMTimecodeToNs(webm_timecode, segment_info_.timecode_scale()),
|
||||||
return time_ns * info_->time_scale() / kSecondsToNs;
|
info_->time_scale());
|
||||||
}
|
}
|
||||||
|
|
||||||
Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
|
Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
|
||||||
|
@ -334,12 +371,12 @@ Status Segmenter::WriteFrame(bool write_duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (write_duration) {
|
if (write_duration) {
|
||||||
const uint64_t duration_ns =
|
frame.set_duration(
|
||||||
prev_sample_->duration() * kSecondsToNs / info_->time_scale();
|
BmffTimestampToNs(prev_sample_->duration(), info_->time_scale()));
|
||||||
frame.set_duration(duration_ns);
|
|
||||||
}
|
}
|
||||||
frame.set_is_key(prev_sample_->is_key_frame());
|
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_);
|
frame.set_track_number(track_id_);
|
||||||
|
|
||||||
if (prev_sample_->side_data_size() > 0) {
|
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()) {
|
if (!prev_sample_->is_key_frame() && !frame.CanBeSimpleBlock()) {
|
||||||
const int64_t timestamp_ns =
|
frame.set_reference_block_timestamp(
|
||||||
reference_frame_timestamp_ * kSecondsToNs / info_->time_scale();
|
BmffTimestampToNs(reference_frame_timestamp_, info_->time_scale()));
|
||||||
frame.set_reference_block_timestamp(timestamp_ns);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRelativeTimecode will return -1 if the relative timecode is too large
|
// GetRelativeTimecode will return -1 if the relative timecode is too large
|
||||||
// to fit in the frame.
|
// to fit in the frame.
|
||||||
if (cluster_->GetRelativeTimecode(frame.timestamp() /
|
if (cluster_->GetRelativeTimecode(NsToWebMTimecode(
|
||||||
cluster_->timecode_scale()) < 0) {
|
frame.timestamp(), cluster_->timecode_scale())) < 0) {
|
||||||
const double segment_duration =
|
const double segment_duration =
|
||||||
static_cast<double>(frame.timestamp()) / kSecondsToNs;
|
static_cast<double>(frame.timestamp()) / kSecondsToNs;
|
||||||
LOG(ERROR) << "Error adding sample to segment: segment too large, "
|
LOG(ERROR) << "Error adding sample to segment: segment too large, "
|
||||||
|
|
|
@ -52,8 +52,8 @@ class Segmenter {
|
||||||
Status AddSample(std::shared_ptr<MediaSample> sample);
|
Status AddSample(std::shared_ptr<MediaSample> sample);
|
||||||
|
|
||||||
/// Finalize the (sub)segment.
|
/// Finalize the (sub)segment.
|
||||||
virtual Status FinalizeSegment(uint64_t start_timescale,
|
virtual Status FinalizeSegment(uint64_t start_timestamp,
|
||||||
uint64_t duration_timescale,
|
uint64_t duration_timestamp,
|
||||||
bool is_subsegment) = 0;
|
bool is_subsegment) = 0;
|
||||||
|
|
||||||
/// @return true if there is an initialization range, while setting @a start
|
/// @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;
|
virtual bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) = 0;
|
||||||
|
|
||||||
/// @return The total length, in seconds, of segmented media files.
|
/// @return The total length, in seconds, of segmented media files.
|
||||||
float GetDuration() const;
|
float GetDurationInSeconds() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Converts the given time in ISO BMFF timescale to the current WebM
|
/// Converts the given time in ISO BMFF timestamp to WebM timecode.
|
||||||
/// timecode.
|
uint64_t FromBmffTimestamp(uint64_t bmff_timestamp);
|
||||||
uint64_t FromBMFFTimescale(uint64_t time_timescale);
|
/// Converts the given time in WebM timecode to ISO BMFF timestamp.
|
||||||
/// Converts the given time in WebM timecode to ISO BMFF timescale.
|
uint64_t FromWebMTimecode(uint64_t webm_timecode);
|
||||||
uint64_t FromWebMTimecode(uint64_t time_webm_timecode);
|
|
||||||
/// Writes the Segment header to @a writer.
|
/// Writes the Segment header to @a writer.
|
||||||
Status WriteSegmentHeader(uint64_t file_size, MkvWriter* writer);
|
Status WriteSegmentHeader(uint64_t file_size, MkvWriter* writer);
|
||||||
/// Creates a Cluster object with the given parameters.
|
/// 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 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
|
// In multi-segment mode, a new file is a segment and the clusters in the file
|
||||||
// are subsegments.
|
// 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.
|
// Store the previous sample so we know which one is the last frame.
|
||||||
std::shared_ptr<MediaSample> prev_sample_;
|
std::shared_ptr<MediaSample> prev_sample_;
|
||||||
|
|
|
@ -22,8 +22,7 @@ const uint8_t kTestMediaSampleSideData[] = {
|
||||||
0x73, 0x69, 0x64, 0x65, 0x00};
|
0x73, 0x69, 0x64, 0x65, 0x00};
|
||||||
|
|
||||||
const int kTrackId = 1;
|
const int kTrackId = 1;
|
||||||
const uint32_t kTimeScale = 1000;
|
const uint64_t kDurationInSeconds = 8;
|
||||||
const uint64_t kDuration = 8000;
|
|
||||||
const Codec kCodec = kCodecVP8;
|
const Codec kCodec = kCodecVP8;
|
||||||
const std::string kCodecString = "vp8";
|
const std::string kCodecString = "vp8";
|
||||||
const std::string kLanguage = "en";
|
const std::string kLanguage = "en";
|
||||||
|
@ -42,7 +41,7 @@ void SegmentTestBase::SetUp() {
|
||||||
SetPackagerVersionForTesting("test");
|
SetPackagerVersionForTesting("test");
|
||||||
|
|
||||||
output_file_name_ = std::string(kMemoryFilePrefix) + "output-file.webm";
|
output_file_name_ = std::string(kMemoryFilePrefix) + "output-file.webm";
|
||||||
cur_time_timescale_ = 0;
|
cur_timestamp_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SegmentTestBase::TearDown() {
|
void SegmentTestBase::TearDown() {
|
||||||
|
@ -64,11 +63,11 @@ std::shared_ptr<MediaSample> SegmentTestBase::CreateSample(
|
||||||
sample = MediaSample::CopyFrom(kTestMediaSampleData,
|
sample = MediaSample::CopyFrom(kTestMediaSampleData,
|
||||||
sizeof(kTestMediaSampleData), is_key_frame);
|
sizeof(kTestMediaSampleData), is_key_frame);
|
||||||
}
|
}
|
||||||
sample->set_dts(cur_time_timescale_);
|
sample->set_dts(cur_timestamp_);
|
||||||
sample->set_pts(cur_time_timescale_);
|
sample->set_pts(cur_timestamp_);
|
||||||
sample->set_duration(duration);
|
sample->set_duration(duration);
|
||||||
|
|
||||||
cur_time_timescale_ += duration;
|
cur_timestamp_ += duration;
|
||||||
return sample;
|
return sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,24 +80,26 @@ MuxerOptions SegmentTestBase::CreateMuxerOptions() const {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoStreamInfo* SegmentTestBase::CreateVideoStreamInfo() const {
|
VideoStreamInfo* SegmentTestBase::CreateVideoStreamInfo(
|
||||||
|
uint32_t time_scale) const {
|
||||||
return new VideoStreamInfo(
|
return new VideoStreamInfo(
|
||||||
kTrackId, kTimeScale, kDuration, kCodec, H26xStreamFormat::kUnSpecified,
|
kTrackId, time_scale, kDurationInSeconds * time_scale, kCodec,
|
||||||
kCodecString, NULL, 0, kWidth, kHeight, kPixelWidth, kPixelHeight,
|
H26xStreamFormat::kUnSpecified, kCodecString, NULL, 0, kWidth, kHeight,
|
||||||
kTrickPlayFactor, kNaluLengthSize, kLanguage, false);
|
kPixelWidth, kPixelHeight, kTrickPlayFactor, kNaluLengthSize, kLanguage,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SegmentTestBase::OutputFileName() const {
|
std::string SegmentTestBase::OutputFileName() const {
|
||||||
return output_file_name_;
|
return output_file_name_;
|
||||||
}
|
}
|
||||||
|
|
||||||
SegmentTestBase::ClusterParser::ClusterParser() : in_cluster_(false) {}
|
SegmentTestBase::ClusterParser::ClusterParser() {}
|
||||||
|
|
||||||
SegmentTestBase::ClusterParser::~ClusterParser() {}
|
SegmentTestBase::ClusterParser::~ClusterParser() {}
|
||||||
|
|
||||||
void SegmentTestBase::ClusterParser::PopulateFromCluster(
|
void SegmentTestBase::ClusterParser::PopulateFromCluster(
|
||||||
const std::string& file_name) {
|
const std::string& file_name) {
|
||||||
cluster_sizes_.clear();
|
frame_timecodes_.clear();
|
||||||
std::string file_contents;
|
std::string file_contents;
|
||||||
ASSERT_TRUE(File::ReadFileToString(file_name.c_str(), &file_contents));
|
ASSERT_TRUE(File::ReadFileToString(file_name.c_str(), &file_contents));
|
||||||
|
|
||||||
|
@ -118,7 +119,7 @@ void SegmentTestBase::ClusterParser::PopulateFromCluster(
|
||||||
|
|
||||||
void SegmentTestBase::ClusterParser::PopulateFromSegment(
|
void SegmentTestBase::ClusterParser::PopulateFromSegment(
|
||||||
const std::string& file_name) {
|
const std::string& file_name) {
|
||||||
cluster_sizes_.clear();
|
frame_timecodes_.clear();
|
||||||
std::string file_contents;
|
std::string file_contents;
|
||||||
ASSERT_TRUE(File::ReadFileToString(file_name.c_str(), &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));
|
0, segment_parser.Parse(data + offset, static_cast<int>(size) - offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
int SegmentTestBase::ClusterParser::GetFrameCountForCluster(size_t i) const {
|
size_t SegmentTestBase::ClusterParser::GetFrameCountForCluster(
|
||||||
DCHECK(i < cluster_sizes_.size());
|
size_t cluster_index) const {
|
||||||
return cluster_sizes_[i];
|
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 {
|
size_t SegmentTestBase::ClusterParser::cluster_count() const {
|
||||||
return cluster_sizes_.size();
|
return frame_timecodes_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
WebMParserClient* SegmentTestBase::ClusterParser::OnListStart(int id) {
|
WebMParserClient* SegmentTestBase::ClusterParser::OnListStart(int id) {
|
||||||
|
@ -147,7 +157,8 @@ WebMParserClient* SegmentTestBase::ClusterParser::OnListStart(int id) {
|
||||||
if (in_cluster_)
|
if (in_cluster_)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
cluster_sizes_.push_back(0);
|
frame_timecodes_.emplace_back();
|
||||||
|
cluster_timecode_ = -1;
|
||||||
in_cluster_ = true;
|
in_cluster_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +176,8 @@ bool SegmentTestBase::ClusterParser::OnListEnd(int id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SegmentTestBase::ClusterParser::OnUInt(int id, int64_t val) {
|
bool SegmentTestBase::ClusterParser::OnUInt(int id, int64_t val) {
|
||||||
|
if (id == kWebMIdTimecode)
|
||||||
|
cluster_timecode_ = val;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,10 +186,15 @@ bool SegmentTestBase::ClusterParser::OnFloat(int id, double val) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SegmentTestBase::ClusterParser::OnBinary(int id,
|
bool SegmentTestBase::ClusterParser::OnBinary(int id,
|
||||||
const uint8_t* data,
|
const uint8_t* data,
|
||||||
int size) {
|
int size) {
|
||||||
if (in_cluster_ && (id == kWebMIdSimpleBlock || id == kWebMIdBlock)) {
|
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;
|
return true;
|
||||||
|
|
|
@ -62,7 +62,7 @@ class SegmentTestBase : public ::testing::Test {
|
||||||
/// Creates a Muxer options object for testing.
|
/// Creates a Muxer options object for testing.
|
||||||
MuxerOptions CreateMuxerOptions() const;
|
MuxerOptions CreateMuxerOptions() const;
|
||||||
/// Creates a video stream info object for testing.
|
/// 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.
|
/// Gets the file name of the current output file.
|
||||||
std::string OutputFileName() const;
|
std::string OutputFileName() const;
|
||||||
|
@ -81,7 +81,8 @@ class SegmentTestBase : public ::testing::Test {
|
||||||
void PopulateFromCluster(const std::string& file_name);
|
void PopulateFromCluster(const std::string& file_name);
|
||||||
void PopulateFromSegment(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;
|
size_t cluster_count() const;
|
||||||
|
|
||||||
|
@ -95,14 +96,18 @@ class SegmentTestBase : public ::testing::Test {
|
||||||
bool OnString(int id, const std::string& str) override;
|
bool OnString(int id, const std::string& str) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<int> cluster_sizes_;
|
int64_t cluster_timecode_ = -1;
|
||||||
bool in_cluster_;
|
// frame_timecodes_[cluster_index][frame_index].
|
||||||
|
std::vector<std::vector<int64_t>> frame_timecodes_;
|
||||||
|
bool in_cluster_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void set_cur_timestamp(uint64_t timestamp) { cur_timestamp_ = timestamp; }
|
||||||
|
|
||||||
std::string output_file_name_;
|
std::string output_file_name_;
|
||||||
std::string segment_template_;
|
std::string segment_template_;
|
||||||
uint64_t cur_time_timescale_;
|
uint64_t cur_timestamp_;
|
||||||
bool single_segment_;
|
bool single_segment_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,11 @@ SingleSegmentSegmenter::SingleSegmentSegmenter(const MuxerOptions& options)
|
||||||
|
|
||||||
SingleSegmentSegmenter::~SingleSegmentSegmenter() {}
|
SingleSegmentSegmenter::~SingleSegmentSegmenter() {}
|
||||||
|
|
||||||
Status SingleSegmentSegmenter::FinalizeSegment(uint64_t start_timescale,
|
Status SingleSegmentSegmenter::FinalizeSegment(uint64_t start_timestamp,
|
||||||
uint64_t duration_timescale,
|
uint64_t duration_timestamp,
|
||||||
bool is_subsegment) {
|
bool is_subsegment) {
|
||||||
Status status = Segmenter::FinalizeSegment(start_timescale,
|
Status status = Segmenter::FinalizeSegment(start_timestamp,
|
||||||
duration_timescale, is_subsegment);
|
duration_timestamp, is_subsegment);
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
// No-op for subsegment in single segment mode.
|
// No-op for subsegment in single segment mode.
|
||||||
|
@ -83,23 +83,23 @@ Status SingleSegmentSegmenter::DoFinalize() {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status SingleSegmentSegmenter::NewSegment(uint64_t start_timescale,
|
Status SingleSegmentSegmenter::NewSegment(uint64_t start_timestamp,
|
||||||
bool is_subsegment) {
|
bool is_subsegment) {
|
||||||
// No-op for subsegment in single segment mode.
|
// No-op for subsegment in single segment mode.
|
||||||
if (is_subsegment)
|
if (is_subsegment)
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
// Create a new Cue point.
|
// Create a new Cue point.
|
||||||
uint64_t position = writer_->Position();
|
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;
|
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_track(track_id());
|
||||||
cue_point->set_cluster_pos(position - segment_payload_pos());
|
cue_point->set_cluster_pos(position - segment_payload_pos());
|
||||||
if (!cues()->AddCue(cue_point))
|
if (!cues()->AddCue(cue_point))
|
||||||
return Status(error::INTERNAL_ERROR, "Error adding CuePoint.");
|
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
|
} // namespace webm
|
||||||
|
|
|
@ -30,8 +30,8 @@ class SingleSegmentSegmenter : public Segmenter {
|
||||||
|
|
||||||
/// @name Segmenter implementation overrides.
|
/// @name Segmenter implementation overrides.
|
||||||
/// @{
|
/// @{
|
||||||
Status FinalizeSegment(uint64_t start_timescale,
|
Status FinalizeSegment(uint64_t start_timestamp,
|
||||||
uint64_t duration_timescale,
|
uint64_t duration_timestamp,
|
||||||
bool is_subsegment) override;
|
bool is_subsegment) override;
|
||||||
bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
||||||
bool GetIndexRangeStartAndEnd(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:
|
private:
|
||||||
// Segmenter implementation overrides.
|
// 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_;
|
std::unique_ptr<MkvWriter> writer_;
|
||||||
uint64_t init_end_;
|
uint64_t init_end_;
|
||||||
|
|
|
@ -12,7 +12,10 @@ namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace {
|
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 bool kSubsegment = true;
|
||||||
|
|
||||||
const uint8_t kBasicSupportData[] = {
|
const uint8_t kBasicSupportData[] = {
|
||||||
|
@ -135,7 +138,7 @@ const uint8_t kBasicSupportData[] = {
|
||||||
|
|
||||||
class SingleSegmentSegmenterTest : public SegmentTestBase {
|
class SingleSegmentSegmenterTest : public SegmentTestBase {
|
||||||
public:
|
public:
|
||||||
SingleSegmentSegmenterTest() : info_(CreateVideoStreamInfo()) {}
|
SingleSegmentSegmenterTest() : info_(CreateVideoStreamInfo(kTimeScale)) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void InitializeSegmenter(const MuxerOptions& options) {
|
void InitializeSegmenter(const MuxerOptions& options) {
|
||||||
|
@ -186,8 +189,8 @@ TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegment) {
|
||||||
ClusterParser parser;
|
ClusterParser parser;
|
||||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName()));
|
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName()));
|
||||||
ASSERT_EQ(2u, parser.cluster_count());
|
ASSERT_EQ(2u, parser.cluster_count());
|
||||||
EXPECT_EQ(5, parser.GetFrameCountForCluster(0));
|
EXPECT_EQ(5u, parser.GetFrameCountForCluster(0));
|
||||||
EXPECT_EQ(3, parser.GetFrameCountForCluster(1));
|
EXPECT_EQ(3u, parser.GetFrameCountForCluster(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) {
|
TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) {
|
||||||
|
@ -209,7 +212,79 @@ TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) {
|
||||||
ClusterParser parser;
|
ClusterParser parser;
|
||||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName()));
|
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName()));
|
||||||
ASSERT_EQ(1u, parser.cluster_count());
|
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
|
} // namespace media
|
||||||
|
|
|
@ -105,7 +105,7 @@ void WebMMuxer::FireOnMediaEndEvent() {
|
||||||
const bool has_index_range = segmenter_->GetIndexRangeStartAndEnd(
|
const bool has_index_range = segmenter_->GetIndexRangeStartAndEnd(
|
||||||
&index_range_start, &index_range_end);
|
&index_range_start, &index_range_end);
|
||||||
|
|
||||||
const float duration_seconds = segmenter_->GetDuration();
|
const float duration_seconds = segmenter_->GetDurationInSeconds();
|
||||||
|
|
||||||
const int64_t file_size =
|
const int64_t file_size =
|
||||||
File::GetFileSize(options().output_file_name.c_str());
|
File::GetFileSize(options().output_file_name.c_str());
|
||||||
|
|
Loading…
Reference in New Issue