diff --git a/media/base/muxer.cc b/media/base/muxer.cc index 86466c1ddf..e634c44915 100644 --- a/media/base/muxer.cc +++ b/media/base/muxer.cc @@ -11,7 +11,10 @@ namespace media { Muxer::Muxer(const MuxerOptions& options) - : options_(options), encryptor_source_(NULL), clear_lead_in_seconds_(0) {} + : options_(options), + encryptor_source_(NULL), + clear_lead_in_seconds_(0), + muxer_listener_(NULL) {} Muxer::~Muxer() {} @@ -57,4 +60,8 @@ Status Muxer::Run() { return status.Matches(Status(error::END_OF_STREAM, "")) ? Status::OK : status; } +void Muxer::SetMuxerListener(media::event::MuxerListener* muxer_listener) { + muxer_listener_ = muxer_listener; +} + } // namespace media diff --git a/media/base/muxer.h b/media/base/muxer.h index a73905f75c..1f1724cc65 100644 --- a/media/base/muxer.h +++ b/media/base/muxer.h @@ -20,6 +20,10 @@ class EncryptorSource; class MediaSample; class MediaStream; +namespace event { +class MuxerListener; +} + class Muxer { public: explicit Muxer(const MuxerOptions& options); @@ -46,12 +50,17 @@ class Muxer { // Drives the remuxing from muxer side (pull). virtual Status Run(); + // Set a MuxerListener event handler for this object. Ownership does not + // transfer. + void SetMuxerListener(event::MuxerListener* muxer_listener); + const std::vector& streams() const { return streams_; } protected: const MuxerOptions& options() const { return options_; } EncryptorSource* encryptor_source() { return encryptor_source_; } double clear_lead_in_seconds() const { return clear_lead_in_seconds_; } + event::MuxerListener* muxer_listener() { return muxer_listener_; } private: MuxerOptions options_; @@ -59,6 +68,8 @@ class Muxer { EncryptorSource* encryptor_source_; double clear_lead_in_seconds_; + event::MuxerListener* muxer_listener_; + DISALLOW_COPY_AND_ASSIGN(Muxer); }; diff --git a/media/mp4/mp4_general_segmenter.cc b/media/mp4/mp4_general_segmenter.cc index b0cf40a369..dcf096943a 100644 --- a/media/mp4/mp4_general_segmenter.cc +++ b/media/mp4/mp4_general_segmenter.cc @@ -55,6 +55,19 @@ Status MP4GeneralSegmenter::Initialize( return status; } +// TODO(rkuroiwa): Maybe GetInitRange() should return true. Init segment does +// exist and we know the size and offset. +bool MP4GeneralSegmenter::GetInitRange(size_t* offset, size_t* size) { + DLOG(INFO) << "MP4GeneralSegmenter outputs init segment: " + << options().output_file_name; + return false; +} + +bool MP4GeneralSegmenter::GetIndexRange(size_t* offset, size_t* size) { + DLOG(INFO) << "MP4GeneralSegmenter does not have index range."; + return false; +} + Status MP4GeneralSegmenter::FinalizeSegment() { Status status = MP4Segmenter::FinalizeSegment(); if (!status.ok()) diff --git a/media/mp4/mp4_general_segmenter.h b/media/mp4/mp4_general_segmenter.h index 35753bb36e..489b0e7a25 100644 --- a/media/mp4/mp4_general_segmenter.h +++ b/media/mp4/mp4_general_segmenter.h @@ -39,6 +39,9 @@ class MP4GeneralSegmenter : public MP4Segmenter { double clear_lead_in_seconds, const std::vector& streams) OVERRIDE; + virtual bool GetInitRange(size_t* offset, size_t* size) OVERRIDE; + virtual bool GetIndexRange(size_t* offset, size_t* size) OVERRIDE; + protected: virtual Status FinalizeSegment() OVERRIDE; diff --git a/media/mp4/mp4_muxer.cc b/media/mp4/mp4_muxer.cc index f7c762cafd..ae64f8f17b 100644 --- a/media/mp4/mp4_muxer.cc +++ b/media/mp4/mp4_muxer.cc @@ -11,6 +11,7 @@ #include "media/base/media_sample.h" #include "media/base/media_stream.h" #include "media/base/video_stream_info.h" +#include "media/event/muxer_listener.h" #include "media/file/file.h" #include "media/mp4/box_definitions.h" #include "media/mp4/es_descriptor.h" @@ -28,6 +29,16 @@ uint64 IsoTimeNow() { const uint64 kIsomTimeOffset = 2082844800l; return kIsomTimeOffset + base::Time::Now().ToDoubleT(); } + +// Sets the range start and end value from offset and size. +// |start| and |end| are for byte-range-spec specified in RFC2616. +void SetStartAndEndFromOffsetAndSize(size_t offset, size_t size, + uint32* start, uint32* end) { + DCHECK(start && end); + *start = static_cast(offset); + // Note that ranges are inclusive. So we need - 1. + *end = *start + static_cast(size) - 1; +} } // namespace namespace media { @@ -96,13 +107,26 @@ Status MP4Muxer::Initialize() { segmenter_.reset( new MP4GeneralSegmenter(options(), ftyp.Pass(), moov.Pass())); } - return segmenter_->Initialize( + + Status segmenter_initialized = segmenter_->Initialize( encryptor_source(), clear_lead_in_seconds(), streams()); + + if (!segmenter_initialized.ok()) + return segmenter_initialized; + + FireOnMediaStartEvent(); + return Status::OK; } Status MP4Muxer::Finalize() { DCHECK(segmenter_ != NULL); - return segmenter_->Finalize(); + Status segmenter_finalized = segmenter_->Finalize(); + + if (!segmenter_finalized.ok()) + return segmenter_finalized; + + FireOnMediaEndEvent(); + return Status::OK; } Status MP4Muxer::AddSample(const MediaStream* stream, @@ -112,7 +136,7 @@ Status MP4Muxer::AddSample(const MediaStream* stream, } void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) { - uint64 now = IsoTimeNow(); + int64 now = IsoTimeNow(); trak->header.creation_time = now; trak->header.modification_time = now; trak->header.duration = 0; @@ -216,5 +240,88 @@ void MP4Muxer::GenerateSinf(ProtectionSchemeInfo* sinf, FourCC old_type) { sinf->info.track_encryption.default_kid = encryptor_source()->key_id(); } +void MP4Muxer::GetStreamInfo(std::vector* stream_infos) { + DCHECK(stream_infos); + const std::vector& media_stream_vec = streams(); + stream_infos->reserve(media_stream_vec.size()); + for (size_t i = 0; i < media_stream_vec.size(); ++i) { + stream_infos->push_back(media_stream_vec[i]->info().get()); + } +} + +bool MP4Muxer::GetInitRangeStartAndEnd(uint32* start, uint32* end) { + DCHECK(start && end); + size_t range_offset = 0; + size_t range_size = 0; + const bool has_range = + segmenter_->GetInitRange(&range_offset, &range_size); + + if (!has_range) + return false; + + SetStartAndEndFromOffsetAndSize(range_offset, range_size, start, end); + return true; +} + +bool MP4Muxer::GetIndexRangeStartAndEnd(uint32* start, uint32* end) { + DCHECK(start && end); + size_t range_offset = 0; + size_t range_size = 0; + const bool has_range = + segmenter_->GetIndexRange(&range_offset, &range_size); + + if (!has_range) + return false; + + SetStartAndEndFromOffsetAndSize(range_offset, range_size, start, end); + return true; +} + +void MP4Muxer::FireOnMediaStartEvent() { + if (!muxer_listener()) + return; + + std::vector stream_info_vec; + GetStreamInfo(&stream_info_vec); + const uint32 timescale = segmenter_->GetReferenceTimeScale(); + muxer_listener()->OnMediaStart(options(), stream_info_vec, timescale); +} + +void MP4Muxer::FireOnMediaEndEvent() { + if (!muxer_listener()) + return; + + std::vector stream_info_vec; + GetStreamInfo(&stream_info_vec); + + uint32 init_range_start = 0; + uint32 init_range_end = 0; + const bool has_init_range = + GetInitRangeStartAndEnd(&init_range_start, &init_range_end); + + uint32 index_range_start = 0; + uint32 index_range_end = 0; + const bool has_index_range = + GetIndexRangeStartAndEnd(&index_range_start, &index_range_end); + + const float duration_seconds = static_cast(segmenter_->GetDuration()); + + const int64 file_size = File::GetFileSize(options().output_file_name.c_str()); + if (file_size <= 0) { + LOG(ERROR) << "Invalid file size: " << file_size; + return; + } + + muxer_listener()->OnMediaEnd(stream_info_vec, + has_init_range, + init_range_start, + init_range_end, + has_index_range, + index_range_start, + index_range_end, + duration_seconds, + file_size); +} + } // namespace mp4 } // namespace media diff --git a/media/mp4/mp4_muxer.h b/media/mp4/mp4_muxer.h index 044ce81068..7c79974e66 100644 --- a/media/mp4/mp4_muxer.h +++ b/media/mp4/mp4_muxer.h @@ -7,6 +7,8 @@ #ifndef MEDIA_MP4_MP4_MUXER_H_ #define MEDIA_MP4_MP4_MUXER_H_ +#include + #include "media/base/muxer.h" #include "media/mp4/fourccs.h" @@ -54,6 +56,21 @@ class MP4Muxer : public Muxer { // Should we enable encrytion? bool IsEncryptionRequired() { return (encryptor_source() != NULL); } + // Helper functions for events. + void GetStreamInfo(std::vector* stream_infos); + + // Gets |start| and |end| initialization range. Returns true if there is an + // init range and sets start-end byte-range-spec specified in RFC2616. + bool GetInitRangeStartAndEnd(uint32* start, uint32* end); + + // Gets |start| and |end| index range. Returns true if there is an index range + // and sets start-end byte-range-spec specified in RFC2616. + bool GetIndexRangeStartAndEnd(uint32* start, uint32* end); + + // Fire events if there are no errors and Muxer::muxer_listener() is not NULL. + void FireOnMediaStartEvent(); + void FireOnMediaEndEvent(); + scoped_ptr segmenter_; DISALLOW_COPY_AND_ASSIGN(MP4Muxer); diff --git a/media/mp4/mp4_segmenter.cc b/media/mp4/mp4_segmenter.cc index a4a0b055e8..110f49eaa5 100644 --- a/media/mp4/mp4_segmenter.cc +++ b/media/mp4/mp4_segmenter.cc @@ -164,6 +164,19 @@ Status MP4Segmenter::AddSample(const MediaStream* stream, return Status::OK; } +uint32 MP4Segmenter::GetReferenceTimeScale() const { + return moov_->header.timescale; +} + +double MP4Segmenter::GetDuration() const { + if (moov_->header.timescale == 0) { + // Handling the case where this is not properly initialized. + return 0.0; + } + + return static_cast(moov_->header.duration) / moov_->header.timescale; +} + void MP4Segmenter::InitializeSegment() { sidx_->references.clear(); end_of_segment_ = false; diff --git a/media/mp4/mp4_segmenter.h b/media/mp4/mp4_segmenter.h index 23036434ad..c70a5c8dee 100644 --- a/media/mp4/mp4_segmenter.h +++ b/media/mp4/mp4_segmenter.h @@ -47,6 +47,8 @@ class MP4Segmenter { // Initialize the segmenter. Caller retains the ownership of // |encryptor_source|. |encryptor_source| can be NULL. + // Calling other public methods of this class without this method returning + // Status::OK, results in an undefined behavior. virtual Status Initialize(EncryptorSource* encryptor_source, double clear_lead_in_seconds, const std::vector& streams); @@ -56,6 +58,21 @@ class MP4Segmenter { virtual Status AddSample(const MediaStream* stream, scoped_refptr sample); + // Returns false if it does not apply. + // If it has an initialization byte range this returns true and set |offset| + // and |size|, otherwise returns false. + virtual bool GetInitRange(size_t* offset, size_t* size) = 0; + + // Returns false if it does not apply. + // If it has an index byte range this returns true and set |offset| and + // |size|, otherwise returns false. + virtual bool GetIndexRange(size_t* offset, size_t* size) = 0; + + uint32 GetReferenceTimeScale() const; + + // Returns the total length, in seconds, of segmented media files. + double GetDuration() const; + protected: void InitializeSegment(); virtual Status FinalizeSegment(); diff --git a/media/mp4/mp4_vod_segmenter.cc b/media/mp4/mp4_vod_segmenter.cc index 2bc8e2f2dd..de58078c6d 100644 --- a/media/mp4/mp4_vod_segmenter.cc +++ b/media/mp4/mp4_vod_segmenter.cc @@ -89,6 +89,21 @@ Status MP4VODSegmenter::Finalize() { return Status::OK; } +bool MP4VODSegmenter::GetInitRange(size_t* offset, size_t* size) { + // In Finalize, ftyp and moov gets written first so offset must be 0. + *offset = 0; + *size = ftyp()->ComputeSize() + moov()->ComputeSize(); + return true; +} + +bool MP4VODSegmenter::GetIndexRange(size_t* offset, size_t* size) { + // Index range is right after init range so the offset must be the size of + // ftyp and moov. + *offset = ftyp()->ComputeSize() + moov()->ComputeSize(); + *size = vod_sidx_->ComputeSize(); + return true; +} + Status MP4VODSegmenter::FinalizeSegment() { Status status = MP4Segmenter::FinalizeSegment(); if (!status.ok()) diff --git a/media/mp4/mp4_vod_segmenter.h b/media/mp4/mp4_vod_segmenter.h index cb3b6ea05d..d4b7a70c2d 100644 --- a/media/mp4/mp4_vod_segmenter.h +++ b/media/mp4/mp4_vod_segmenter.h @@ -37,6 +37,9 @@ class MP4VODSegmenter : public MP4Segmenter { const std::vector& streams) OVERRIDE; virtual Status Finalize() OVERRIDE; + virtual bool GetInitRange(size_t* offset, size_t* size) OVERRIDE; + virtual bool GetIndexRange(size_t* offset, size_t* size) OVERRIDE; + protected: virtual Status FinalizeSegment() OVERRIDE;