Add ProgressListener for progress updates

Bug: 19730025
Change-Id: If60d7629c69d84b245f0c96fb564704d7af82ea2
This commit is contained in:
Kongqun Yang 2015-05-11 14:07:10 -07:00
parent e9f402a328
commit 8277236fe7
8 changed files with 130 additions and 8 deletions

View File

@ -83,6 +83,11 @@ void Muxer::SetMuxerListener(scoped_ptr<MuxerListener> muxer_listener) {
muxer_listener_ = muxer_listener.Pass(); muxer_listener_ = muxer_listener.Pass();
} }
void Muxer::SetProgressListener(
scoped_ptr<ProgressListener> progress_listener) {
progress_listener_ = progress_listener.Pass();
}
Status Muxer::AddSample(const MediaStream* stream, Status Muxer::AddSample(const MediaStream* stream,
scoped_refptr<MediaSample> sample) { scoped_refptr<MediaSample> sample) {
DCHECK(std::find(streams_.begin(), streams_.end(), stream) != streams_.end()); DCHECK(std::find(streams_.begin(), streams_.end(), stream) != streams_.end());

View File

@ -17,6 +17,7 @@
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/base/status.h" #include "packager/media/base/status.h"
#include "packager/media/event/muxer_listener.h" #include "packager/media/event/muxer_listener.h"
#include "packager/media/event/progress_listener.h"
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
@ -62,6 +63,10 @@ class Muxer {
/// @param muxer_listener should not be NULL. /// @param muxer_listener should not be NULL.
void SetMuxerListener(scoped_ptr<MuxerListener> muxer_listener); void SetMuxerListener(scoped_ptr<MuxerListener> muxer_listener);
/// Set a ProgressListener event handler for this object.
/// @param progress_listener should not be NULL.
void SetProgressListener(scoped_ptr<ProgressListener> progress_listener);
const std::vector<MediaStream*>& streams() const { return streams_; } const std::vector<MediaStream*>& streams() const { return streams_; }
/// Inject clock, mainly used for testing. /// Inject clock, mainly used for testing.
@ -85,6 +90,7 @@ class Muxer {
return crypto_period_duration_in_seconds_; return crypto_period_duration_in_seconds_;
} }
MuxerListener* muxer_listener() { return muxer_listener_.get(); } MuxerListener* muxer_listener() { return muxer_listener_.get(); }
ProgressListener* progress_listener() { return progress_listener_.get(); }
base::Clock* clock() { return clock_; } base::Clock* clock() { return clock_; }
private: private:
@ -114,6 +120,7 @@ class Muxer {
bool cancelled_; bool cancelled_;
scoped_ptr<MuxerListener> muxer_listener_; scoped_ptr<MuxerListener> muxer_listener_;
scoped_ptr<ProgressListener> progress_listener_;
// An external injected clock, can be NULL. // An external injected clock, can be NULL.
base::Clock* clock_; base::Clock* clock_;

View File

@ -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 <stdint.h>
#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_

View File

@ -102,6 +102,7 @@ Status MP4Muxer::Initialize() {
Status segmenter_initialized = Status segmenter_initialized =
segmenter_->Initialize(streams(), segmenter_->Initialize(streams(),
muxer_listener(), muxer_listener(),
progress_listener(),
encryption_key_source(), encryption_key_source(),
max_sd_pixels(), max_sd_pixels(),
clear_lead_in_seconds(), clear_lead_in_seconds(),

View File

@ -65,6 +65,7 @@ Status MultiSegmentSegmenter::DoInitialize() {
} }
Status MultiSegmentSegmenter::DoFinalize() { Status MultiSegmentSegmenter::DoFinalize() {
SetComplete();
return Status::OK; return Status::OK;
} }
@ -173,17 +174,22 @@ Status MultiSegmentSegmenter::WriteSegment() {
if (!file->Close()) if (!file->Close())
LOG(WARNING) << "Failed to close the file properly: " << file_name; LOG(WARNING) << "Failed to close the file properly: " << file_name;
if (status.ok() && muxer_listener()) { if (!status.ok())
uint64_t segment_duration = 0; return status;
// 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. uint64_t segment_duration = 0;
for (size_t i = 0; i < sidx()->references.size(); ++i) // ISO/IEC 23009-1:2012: the value shall be identical to sum of the the
segment_duration += sidx()->references[i].subsegment_duration; // 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( muxer_listener()->OnNewSegment(
sidx()->earliest_presentation_time, segment_duration, segment_size); sidx()->earliest_presentation_time, segment_duration, segment_size);
} }
return status; return Status::OK;
} }
} // namespace mp4 } // namespace mp4

View File

@ -15,6 +15,7 @@
#include "packager/media/base/media_stream.h" #include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/base/video_stream_info.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/box_definitions.h"
#include "packager/media/formats/mp4/key_rotation_fragmenter.h" #include "packager/media/formats/mp4/key_rotation_fragmenter.h"
@ -123,18 +124,24 @@ Segmenter::Segmenter(const MuxerOptions& options,
sidx_(new SegmentIndex()), sidx_(new SegmentIndex()),
segment_initialized_(false), segment_initialized_(false),
end_of_segment_(false), end_of_segment_(false),
muxer_listener_(NULL) {} muxer_listener_(NULL),
progress_listener_(NULL),
progress_target_(0),
accumulated_progress_(0) {
}
Segmenter::~Segmenter() { STLDeleteElements(&fragmenters_); } Segmenter::~Segmenter() { STLDeleteElements(&fragmenters_); }
Status Segmenter::Initialize(const std::vector<MediaStream*>& streams, Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
MuxerListener* muxer_listener, MuxerListener* muxer_listener,
ProgressListener* progress_listener,
KeySource* encryption_key_source, KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
double clear_lead_in_seconds, double clear_lead_in_seconds,
double crypto_period_duration_in_seconds) { double crypto_period_duration_in_seconds) {
DCHECK_LT(0u, streams.size()); DCHECK_LT(0u, streams.size());
muxer_listener_ = muxer_listener; muxer_listener_ = muxer_listener;
progress_listener_ = progress_listener;
moof_->header.sequence_number = 0; moof_->header.sequence_number = 0;
moof_->tracks.resize(streams.size()); moof_->tracks.resize(streams.size());
@ -202,6 +209,9 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
sidx_->reference_id = 1; sidx_->reference_id = 1;
sidx_->timescale = streams[GetReferenceStreamId()]->info()->time_scale(); 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. // Use the reference stream's time scale as movie time scale.
moov_->header.timescale = sidx_->timescale; moov_->header.timescale = sidx_->timescale;
moof_->header.sequence_number = 1; moof_->header.sequence_number = 1;
@ -303,6 +313,27 @@ double Segmenter::GetDuration() const {
return static_cast<double>(moov_->header.duration) / moov_->header.timescale; return static_cast<double>(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<double>(accumulated_progress_) /
progress_target_);
}
}
void Segmenter::SetComplete() {
if (!progress_listener_) return;
progress_listener_->OnProgress(1.0);
}
void Segmenter::InitializeSegment() { void Segmenter::InitializeSegment() {
sidx_->references.clear(); sidx_->references.clear();
end_of_segment_ = false; end_of_segment_ = false;

View File

@ -24,6 +24,7 @@ class KeySource;
class MediaSample; class MediaSample;
class MediaStream; class MediaStream;
class MuxerListener; class MuxerListener;
class ProgressListener;
namespace mp4 { namespace mp4 {
@ -51,6 +52,8 @@ class Segmenter {
/// Calling other public methods of this class without this method returning /// Calling other public methods of this class without this method returning
/// Status::OK results in an undefined behavior. /// Status::OK results in an undefined behavior.
/// @param streams contains the vector of MediaStreams to be segmented. /// @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 /// @param encryption_key_source points to the key source which contains
/// the encryption keys. It can be NULL to indicate that no encryption /// the encryption keys. It can be NULL to indicate that no encryption
/// is required. /// is required.
@ -62,6 +65,7 @@ class Segmenter {
/// @return OK on success, an error status otherwise. /// @return OK on success, an error status otherwise.
Status Initialize(const std::vector<MediaStream*>& streams, Status Initialize(const std::vector<MediaStream*>& streams,
MuxerListener* muxer_listener, MuxerListener* muxer_listener,
ProgressListener* progress_listener,
KeySource* encryption_key_source, KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
double clear_lead_in_seconds, double clear_lead_in_seconds,
@ -93,12 +97,22 @@ class Segmenter {
double GetDuration() const; double GetDuration() const;
protected: protected:
/// Update segmentation progress using ProgressListener.
void UpdateProgress(uint64_t progress);
/// Set progress to 100%.
void SetComplete();
const MuxerOptions& options() const { return options_; } const MuxerOptions& options() const { return options_; }
FileType* ftyp() { return ftyp_.get(); } FileType* ftyp() { return ftyp_.get(); }
Movie* moov() { return moov_.get(); } Movie* moov() { return moov_.get(); }
BufferWriter* fragment_buffer() { return fragment_buffer_.get(); } BufferWriter* fragment_buffer() { return fragment_buffer_.get(); }
SegmentIndex* sidx() { return sidx_.get(); } SegmentIndex* sidx() { return sidx_.get(); }
MuxerListener* muxer_listener() { return muxer_listener_; } 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: private:
virtual Status DoInitialize() = 0; virtual Status DoInitialize() = 0;
@ -123,6 +137,9 @@ class Segmenter {
bool segment_initialized_; bool segment_initialized_;
bool end_of_segment_; bool end_of_segment_;
MuxerListener* muxer_listener_; MuxerListener* muxer_listener_;
ProgressListener* progress_listener_;
uint64_t progress_target_;
uint64_t accumulated_progress_;
DISALLOW_COPY_AND_ASSIGN(Segmenter); DISALLOW_COPY_AND_ASSIGN(Segmenter);
}; };

View File

@ -18,6 +18,7 @@
#include "packager/media/base/media_stream.h" #include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/event/muxer_listener.h" #include "packager/media/event/muxer_listener.h"
#include "packager/media/event/progress_listener.h"
#include "packager/media/file/file.h" #include "packager/media/file/file.h"
#include "packager/media/formats/mp4/box_definitions.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() { 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()) { if (options().temp_dir.empty()) {
base::FilePath temp_file_path; base::FilePath temp_file_path;
if (!base::CreateTemporaryFile(&temp_file_path)) { if (!base::CreateTemporaryFile(&temp_file_path)) {
@ -122,6 +131,9 @@ Status SingleSegmentSegmenter::DoFinalize() {
"Cannot open file to read " + temp_file_name_); "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. const int kBufSize = 0x200000; // 2MB.
scoped_ptr<uint8_t[]> buf(new uint8_t[kBufSize]); scoped_ptr<uint8_t[]> buf(new uint8_t[kBufSize]);
while (true) { while (true) {
@ -137,7 +149,10 @@ Status SingleSegmentSegmenter::DoFinalize() {
return Status(error::FILE_FAILURE, return Status(error::FILE_FAILURE,
"Failed to write file " + options().output_file_name); "Failed to write file " + options().output_file_name);
} }
UpdateProgress(static_cast<double>(size) / temp_file->Size() *
re_segment_progress_target);
} }
SetComplete();
return Status::OK; return Status::OK;
} }
@ -204,6 +219,8 @@ Status SingleSegmentSegmenter::DoFinalizeSegment() {
size_t segment_size = fragment_buffer()->Size(); size_t segment_size = fragment_buffer()->Size();
Status status = fragment_buffer()->WriteToFile(temp_file_.get()); Status status = fragment_buffer()->WriteToFile(temp_file_.get());
if (!status.ok()) return status; if (!status.ok()) return status;
UpdateProgress(vod_ref.subsegment_duration);
if (muxer_listener()) { if (muxer_listener()) {
muxer_listener()->OnNewSegment(vod_ref.earliest_presentation_time, muxer_listener()->OnNewSegment(vod_ref.earliest_presentation_time,
vod_ref.subsegment_duration, segment_size); vod_ref.subsegment_duration, segment_size);