diff --git a/packager/media/base/muxer.cc b/packager/media/base/muxer.cc index 8e084e7169..61310d5968 100644 --- a/packager/media/base/muxer.cc +++ b/packager/media/base/muxer.cc @@ -83,6 +83,11 @@ void Muxer::SetMuxerListener(scoped_ptr muxer_listener) { muxer_listener_ = muxer_listener.Pass(); } +void Muxer::SetProgressListener( + scoped_ptr progress_listener) { + progress_listener_ = progress_listener.Pass(); +} + Status Muxer::AddSample(const MediaStream* stream, scoped_refptr sample) { DCHECK(std::find(streams_.begin(), streams_.end(), stream) != streams_.end()); diff --git a/packager/media/base/muxer.h b/packager/media/base/muxer.h index ddf0fd282c..27d10844e0 100644 --- a/packager/media/base/muxer.h +++ b/packager/media/base/muxer.h @@ -17,6 +17,7 @@ #include "packager/media/base/muxer_options.h" #include "packager/media/base/status.h" #include "packager/media/event/muxer_listener.h" +#include "packager/media/event/progress_listener.h" namespace edash_packager { namespace media { @@ -62,6 +63,10 @@ class Muxer { /// @param muxer_listener should not be NULL. void SetMuxerListener(scoped_ptr muxer_listener); + /// Set a ProgressListener event handler for this object. + /// @param progress_listener should not be NULL. + void SetProgressListener(scoped_ptr progress_listener); + const std::vector& streams() const { return streams_; } /// Inject clock, mainly used for testing. @@ -85,6 +90,7 @@ class Muxer { return crypto_period_duration_in_seconds_; } MuxerListener* muxer_listener() { return muxer_listener_.get(); } + ProgressListener* progress_listener() { return progress_listener_.get(); } base::Clock* clock() { return clock_; } private: @@ -114,6 +120,7 @@ class Muxer { bool cancelled_; scoped_ptr muxer_listener_; + scoped_ptr progress_listener_; // An external injected clock, can be NULL. base::Clock* clock_; diff --git a/packager/media/event/progress_listener.h b/packager/media/event/progress_listener.h new file mode 100644 index 0000000000..2c9268a0ed --- /dev/null +++ b/packager/media/event/progress_listener.h @@ -0,0 +1,38 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd +// +// Event handler for progress updates. + +#ifndef MEDIA_EVENT_PROGRESS_LISTENER_H_ +#define MEDIA_EVENT_PROGRESS_LISTENER_H_ + +#include + +#include "packager/base/macros.h" + +namespace edash_packager { +namespace media { + +/// This class listens to progress updates events. +class ProgressListener { + public: + virtual ~ProgressListener() {} + + /// Called when there is a progress update. + /// @param progress is the current progress metric, ranges from 0 to 1. + virtual void OnProgress(double progress) = 0; + + protected: + ProgressListener() {} + + private: + DISALLOW_COPY_AND_ASSIGN(ProgressListener); +}; + +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_EVENT_PROGRESS_LISTENER_H_ diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 96709d6194..63a57d730e 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -102,6 +102,7 @@ Status MP4Muxer::Initialize() { Status segmenter_initialized = segmenter_->Initialize(streams(), muxer_listener(), + progress_listener(), encryption_key_source(), max_sd_pixels(), clear_lead_in_seconds(), diff --git a/packager/media/formats/mp4/multi_segment_segmenter.cc b/packager/media/formats/mp4/multi_segment_segmenter.cc index cc9311e7a2..bc6dbe6abc 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.cc +++ b/packager/media/formats/mp4/multi_segment_segmenter.cc @@ -65,6 +65,7 @@ Status MultiSegmentSegmenter::DoInitialize() { } Status MultiSegmentSegmenter::DoFinalize() { + SetComplete(); return Status::OK; } @@ -173,17 +174,22 @@ Status MultiSegmentSegmenter::WriteSegment() { if (!file->Close()) LOG(WARNING) << "Failed to close the file properly: " << file_name; - if (status.ok() && muxer_listener()) { - uint64_t segment_duration = 0; - // ISO/IEC 23009-1:2012: the value shall be identical to sum of the the - // values of all Subsegment_duration fields in the first ‘sidx’ box. - for (size_t i = 0; i < sidx()->references.size(); ++i) - segment_duration += sidx()->references[i].subsegment_duration; + if (!status.ok()) + return status; + + uint64_t segment_duration = 0; + // ISO/IEC 23009-1:2012: the value shall be identical to sum of the the + // values of all Subsegment_duration fields in the first ‘sidx’ box. + for (size_t i = 0; i < sidx()->references.size(); ++i) + segment_duration += sidx()->references[i].subsegment_duration; + + UpdateProgress(segment_duration); + if (muxer_listener()) { muxer_listener()->OnNewSegment( sidx()->earliest_presentation_time, segment_duration, segment_size); } - return status; + return Status::OK; } } // namespace mp4 diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index eefac71ffd..12e8e2daf3 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -15,6 +15,7 @@ #include "packager/media/base/media_stream.h" #include "packager/media/base/muxer_options.h" #include "packager/media/base/video_stream_info.h" +#include "packager/media/event/progress_listener.h" #include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/key_rotation_fragmenter.h" @@ -123,18 +124,24 @@ Segmenter::Segmenter(const MuxerOptions& options, sidx_(new SegmentIndex()), segment_initialized_(false), end_of_segment_(false), - muxer_listener_(NULL) {} + muxer_listener_(NULL), + progress_listener_(NULL), + progress_target_(0), + accumulated_progress_(0) { +} Segmenter::~Segmenter() { STLDeleteElements(&fragmenters_); } Status Segmenter::Initialize(const std::vector& streams, MuxerListener* muxer_listener, + ProgressListener* progress_listener, KeySource* encryption_key_source, uint32_t max_sd_pixels, double clear_lead_in_seconds, double crypto_period_duration_in_seconds) { DCHECK_LT(0u, streams.size()); muxer_listener_ = muxer_listener; + progress_listener_ = progress_listener; moof_->header.sequence_number = 0; moof_->tracks.resize(streams.size()); @@ -202,6 +209,9 @@ Status Segmenter::Initialize(const std::vector& streams, sidx_->reference_id = 1; sidx_->timescale = streams[GetReferenceStreamId()]->info()->time_scale(); + // Use media duration as progress target. + progress_target_ = streams[GetReferenceStreamId()]->info()->duration(); + // Use the reference stream's time scale as movie time scale. moov_->header.timescale = sidx_->timescale; moof_->header.sequence_number = 1; @@ -303,6 +313,27 @@ double Segmenter::GetDuration() const { return static_cast(moov_->header.duration) / moov_->header.timescale; } +void Segmenter::UpdateProgress(uint64_t progress) { + accumulated_progress_ += progress; + + if (!progress_listener_) return; + if (progress_target_ == 0) return; + // It might happen that accumulated progress exceeds progress_target due to + // computation errors, e.g. rounding error. Cap it so it never reports > 100% + // progress. + if (accumulated_progress_ >= progress_target_) { + progress_listener_->OnProgress(1.0); + } else { + progress_listener_->OnProgress(static_cast(accumulated_progress_) / + progress_target_); + } +} + +void Segmenter::SetComplete() { + if (!progress_listener_) return; + progress_listener_->OnProgress(1.0); +} + void Segmenter::InitializeSegment() { sidx_->references.clear(); end_of_segment_ = false; diff --git a/packager/media/formats/mp4/segmenter.h b/packager/media/formats/mp4/segmenter.h index 9fdaa9ec46..3cfca3c229 100644 --- a/packager/media/formats/mp4/segmenter.h +++ b/packager/media/formats/mp4/segmenter.h @@ -24,6 +24,7 @@ class KeySource; class MediaSample; class MediaStream; class MuxerListener; +class ProgressListener; namespace mp4 { @@ -51,6 +52,8 @@ class Segmenter { /// Calling other public methods of this class without this method returning /// Status::OK results in an undefined behavior. /// @param streams contains the vector of MediaStreams to be segmented. + /// @param muxer_listener receives muxer events. Can be NULL. + /// @param progress_listener receives progress updates. Can be NULL. /// @param encryption_key_source points to the key source which contains /// the encryption keys. It can be NULL to indicate that no encryption /// is required. @@ -62,6 +65,7 @@ class Segmenter { /// @return OK on success, an error status otherwise. Status Initialize(const std::vector& streams, MuxerListener* muxer_listener, + ProgressListener* progress_listener, KeySource* encryption_key_source, uint32_t max_sd_pixels, double clear_lead_in_seconds, @@ -93,12 +97,22 @@ class Segmenter { double GetDuration() const; protected: + /// Update segmentation progress using ProgressListener. + void UpdateProgress(uint64_t progress); + /// Set progress to 100%. + void SetComplete(); + const MuxerOptions& options() const { return options_; } FileType* ftyp() { return ftyp_.get(); } Movie* moov() { return moov_.get(); } BufferWriter* fragment_buffer() { return fragment_buffer_.get(); } SegmentIndex* sidx() { return sidx_.get(); } MuxerListener* muxer_listener() { return muxer_listener_; } + uint64_t progress_target() { return progress_target_; } + + void set_progress_target(uint64_t progress_target) { + progress_target_ = progress_target; + } private: virtual Status DoInitialize() = 0; @@ -123,6 +137,9 @@ class Segmenter { bool segment_initialized_; bool end_of_segment_; MuxerListener* muxer_listener_; + ProgressListener* progress_listener_; + uint64_t progress_target_; + uint64_t accumulated_progress_; DISALLOW_COPY_AND_ASSIGN(Segmenter); }; diff --git a/packager/media/formats/mp4/single_segment_segmenter.cc b/packager/media/formats/mp4/single_segment_segmenter.cc index f59706ec2d..4bbd343b66 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.cc +++ b/packager/media/formats/mp4/single_segment_segmenter.cc @@ -18,6 +18,7 @@ #include "packager/media/base/media_stream.h" #include "packager/media/base/muxer_options.h" #include "packager/media/event/muxer_listener.h" +#include "packager/media/event/progress_listener.h" #include "packager/media/file/file.h" #include "packager/media/formats/mp4/box_definitions.h" @@ -68,6 +69,14 @@ bool SingleSegmentSegmenter::GetIndexRange(size_t* offset, size_t* size) { } Status SingleSegmentSegmenter::DoInitialize() { + // Single segment segmentation involves two stages: + // Stage 1: Create media subsegments from media samples + // Stage 2: Update media header (moov) which involves copying of media + // subsegments + // Assumes stage 2 takes similar amount of time as stage 1. The previous + // progress_target was set for stage 1. Times two to account for stage 2. + set_progress_target(progress_target() * 2); + if (options().temp_dir.empty()) { base::FilePath temp_file_path; if (!base::CreateTemporaryFile(&temp_file_path)) { @@ -122,6 +131,9 @@ Status SingleSegmentSegmenter::DoFinalize() { "Cannot open file to read " + temp_file_name_); } + // The target of 2nd stage of single segment segmentation. + const uint64_t re_segment_progress_target = progress_target() * 0.5; + const int kBufSize = 0x200000; // 2MB. scoped_ptr buf(new uint8_t[kBufSize]); while (true) { @@ -137,7 +149,10 @@ Status SingleSegmentSegmenter::DoFinalize() { return Status(error::FILE_FAILURE, "Failed to write file " + options().output_file_name); } + UpdateProgress(static_cast(size) / temp_file->Size() * + re_segment_progress_target); } + SetComplete(); return Status::OK; } @@ -204,6 +219,8 @@ Status SingleSegmentSegmenter::DoFinalizeSegment() { size_t segment_size = fragment_buffer()->Size(); Status status = fragment_buffer()->WriteToFile(temp_file_.get()); if (!status.ok()) return status; + + UpdateProgress(vod_ref.subsegment_duration); if (muxer_listener()) { muxer_listener()->OnNewSegment(vod_ref.earliest_presentation_time, vod_ref.subsegment_duration, segment_size);