From f3ed07a64e1be52d9a7378d2c32f35ad77d8f71e Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Sat, 16 Apr 2016 15:58:47 -0700 Subject: [PATCH] Use MuxerListener in TsMuxer - MuxerListener is used in TsMuxer so that a listener can be used to e.g. generate manifests. Change-Id: I11c745e1c2b71d5ec901387fe42713d4ad69dc03 --- packager/media/event/media_event.gyp | 12 ++++ packager/media/event/mock_muxer_listener.cc | 16 +++++ packager/media/event/mock_muxer_listener.h | 60 +++++++++++++++++++ packager/media/formats/mp2t/mp2t.gyp | 1 + packager/media/formats/mp2t/ts_muxer.cc | 37 ++++++++++-- packager/media/formats/mp2t/ts_muxer.h | 5 +- packager/media/formats/mp2t/ts_segmenter.cc | 15 ++++- packager/media/formats/mp2t/ts_segmenter.h | 14 ++++- .../formats/mp2t/ts_segmenter_unittest.cc | 27 ++++++--- 9 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 packager/media/event/mock_muxer_listener.cc create mode 100644 packager/media/event/mock_muxer_listener.h diff --git a/packager/media/event/media_event.gyp b/packager/media/event/media_event.gyp index 5a7f99285d..fa90fc2275 100644 --- a/packager/media/event/media_event.gyp +++ b/packager/media/event/media_event.gyp @@ -32,6 +32,18 @@ '../filters/filters.gyp:filters', ], }, + { + 'target_name': 'mock_muxer_listener', + 'type': '<(component)', + 'sources': [ + 'mock_muxer_listener.cc', + 'mock_muxer_listener.h', + ], + 'dependencies': [ + '../../testing/gmock.gyp:gmock', + 'media_event', + ], + }, { 'target_name': 'media_event_unittest', 'type': '<(gtest_target_type)', diff --git a/packager/media/event/mock_muxer_listener.cc b/packager/media/event/mock_muxer_listener.cc new file mode 100644 index 0000000000..ab200e52a9 --- /dev/null +++ b/packager/media/event/mock_muxer_listener.cc @@ -0,0 +1,16 @@ +// Copyright 2016 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 + +#include "packager/media/event/mock_muxer_listener.h" + +namespace edash_packager { +namespace media { + +MockMuxerListener::MockMuxerListener() {} +MockMuxerListener::~MockMuxerListener() {} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/event/mock_muxer_listener.h b/packager/media/event/mock_muxer_listener.h new file mode 100644 index 0000000000..b55393df0b --- /dev/null +++ b/packager/media/event/mock_muxer_listener.h @@ -0,0 +1,60 @@ +// Copyright 2016 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 + +#ifndef PACKAGER_MEDIA_EVENT_MOCK_MUXER_LISTENER_H_ +#define PACKAGER_MEDIA_EVENT_MOCK_MUXER_LISTENER_H_ + +#include + +#include "packager/media/base/muxer_options.h" +#include "packager/media/base/protection_system_specific_info.h" +#include "packager/media/base/stream_info.h" +#include "packager/media/event/muxer_listener.h" + +namespace edash_packager { +namespace media { + +class MockMuxerListener : public MuxerListener { + public: + MockMuxerListener(); + ~MockMuxerListener() override; + + MOCK_METHOD4( + OnEncryptionInfoReady, + void(bool is_initial_encryption_info, + const std::vector& key_id, + const std::vector& iv, + const std::vector& key_system_info)); + + MOCK_METHOD4(OnMediaStart, + void(const MuxerOptions& muxer_options, + const StreamInfo& stream_info, + uint32_t time_scale, + ContainerType container_type)); + + MOCK_METHOD1(OnSampleDurationReady, void(uint32_t sample_duration)); + + MOCK_METHOD8(OnMediaEnd, + void(bool has_init_range, + uint64_t init_range_start, + uint64_t init_range_end, + bool has_index_range, + uint64_t index_range_start, + uint64_t index_range_end, + float duration_seconds, + uint64_t file_size)); + + MOCK_METHOD4(OnNewSegment, + void(const std::string& segment_name, + uint64_t start_time, + uint64_t duration, + uint64_t segment_file_size)); +}; + +} // namespace media +} // namespace edash_packager + +#endif // PACKAGER_MEDIA_EVENT_MOCK_MUXER_LISTENER_H_ diff --git a/packager/media/formats/mp2t/mp2t.gyp b/packager/media/formats/mp2t/mp2t.gyp index 5a8c3203c1..7c1fb57e6e 100644 --- a/packager/media/formats/mp2t/mp2t.gyp +++ b/packager/media/formats/mp2t/mp2t.gyp @@ -64,6 +64,7 @@ 'dependencies': [ '../../../testing/gtest.gyp:gtest', '../../../testing/gmock.gyp:gmock', + '../../event/media_event.gyp:mock_muxer_listener', '../../filters/filters.gyp:filters', '../../test/media_test.gyp:media_test_support', '../mpeg/mpeg.gyp:mpeg', diff --git a/packager/media/formats/mp2t/ts_muxer.cc b/packager/media/formats/mp2t/ts_muxer.cc index 092bad46be..19c2b34efd 100644 --- a/packager/media/formats/mp2t/ts_muxer.cc +++ b/packager/media/formats/mp2t/ts_muxer.cc @@ -10,23 +10,50 @@ namespace edash_packager { namespace media { namespace mp2t { -TsMuxer::TsMuxer(const MuxerOptions& muxer_options) - : Muxer(muxer_options), segmenter_(options()) {} +namespace { +const uint32_t kTsTimescale = 90000; +} // namespace + +TsMuxer::TsMuxer(const MuxerOptions& muxer_options) : Muxer(muxer_options) {} TsMuxer::~TsMuxer() {} Status TsMuxer::Initialize() { if (streams().size() > 1u) return Status(error::MUXER_FAILURE, "Cannot handle more than one streams."); - return segmenter_.Initialize(*streams()[0]->info()); + + segmenter_.reset(new TsSegmenter(options(), muxer_listener())); + Status status = segmenter_->Initialize(*streams()[0]->info()); + FireOnMediaStartEvent(); + return status; } Status TsMuxer::Finalize() { - return segmenter_.Finalize(); + FireOnMediaEndEvent(); + return segmenter_->Finalize(); } Status TsMuxer::DoAddSample(const MediaStream* stream, scoped_refptr sample) { - return segmenter_.AddSample(sample); + return segmenter_->AddSample(sample); +} + +void TsMuxer::FireOnMediaStartEvent() { + if (!muxer_listener()) + return; + muxer_listener()->OnMediaStart(options(), *streams().front()->info(), + kTsTimescale, MuxerListener::kContainerWebM); +} + +void TsMuxer::FireOnMediaEndEvent() { + if (!muxer_listener()) + return; + + // For now, there is no single file TS segmenter. So all the values passed + // here are false and 0. Called just to notify the MuxerListener. + const bool kHasInitRange = true; + const bool kHasIndexRange = true; + muxer_listener()->OnMediaEnd(!kHasInitRange, 0, 0, !kHasIndexRange, 0, 0, 0, + 0); } } // namespace mp2t diff --git a/packager/media/formats/mp2t/ts_muxer.h b/packager/media/formats/mp2t/ts_muxer.h index a878c317c0..3ac9bfa9d2 100644 --- a/packager/media/formats/mp2t/ts_muxer.h +++ b/packager/media/formats/mp2t/ts_muxer.h @@ -29,7 +29,10 @@ class TsMuxer : public Muxer { Status DoAddSample(const MediaStream* stream, scoped_refptr sample) override; - TsSegmenter segmenter_; + void FireOnMediaStartEvent(); + void FireOnMediaEndEvent(); + + scoped_ptr segmenter_; DISALLOW_COPY_AND_ASSIGN(TsMuxer); }; diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index c26d37e26c..5910f8232d 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -10,6 +10,7 @@ #include "packager/media/base/muxer_util.h" #include "packager/media/base/status.h" +#include "packager/media/event/muxer_listener.h" namespace edash_packager { namespace media { @@ -19,8 +20,9 @@ namespace { const double kTsTimescale = 90000; } // namespace -TsSegmenter::TsSegmenter(const MuxerOptions& options) +TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener) : muxer_options_(options), + listener_(listener), ts_writer_(new TsWriter()), pes_packet_generator_(new PesPacketGenerator()) {} TsSegmenter::~TsSegmenter() {} @@ -91,6 +93,8 @@ Status TsSegmenter::OpenNewSegmentIfClosed(uint32_t next_pts) { segment_number_++, muxer_options_.bandwidth); if (!ts_writer_->NewSegment(segment_name)) return Status(error::MUXER_FAILURE, "Failed to initilize TsPacketWriter."); + current_segment_start_time_ = next_pts; + current_segment_path_ = segment_name; ts_writer_file_opened_ = true; return Status::OK; } @@ -125,9 +129,18 @@ Status TsSegmenter::Flush() { if (!ts_writer_->FinalizeSegment()) { return Status(error::MUXER_FAILURE, "Failed to finalize TsWriter."); } + if (listener_) { + const int64_t file_size = + File::GetFileSize(current_segment_path_.c_str()); + listener_->OnNewSegment( + current_segment_path_, current_segment_start_time_, + current_segment_total_sample_duration_ * kTsTimescale, file_size); + } ts_writer_file_opened_ = false; } current_segment_total_sample_duration_ = 0.0; + current_segment_start_time_ = 0; + current_segment_path_.clear(); return Status::OK; } diff --git a/packager/media/formats/mp2t/ts_segmenter.h b/packager/media/formats/mp2t/ts_segmenter.h index 57ae232ac4..5a13562db3 100644 --- a/packager/media/formats/mp2t/ts_segmenter.h +++ b/packager/media/formats/mp2t/ts_segmenter.h @@ -17,6 +17,9 @@ namespace edash_packager { namespace media { + +class MuxerListener; + namespace mp2t { // TODO(rkuroiwa): For now, this implements multifile segmenter. Like other @@ -26,7 +29,9 @@ class TsSegmenter { public: /// @param options is the options for this muxer. This must stay valid /// throughout the life time of the instance. - explicit TsSegmenter(const MuxerOptions& options); + /// @param listener is the MuxerListener that should be used to notify events. + /// This may be null, in which case no events are sent. + TsSegmenter(const MuxerOptions& options, MuxerListener* listener); ~TsSegmenter(); /// Initialize the object. @@ -65,6 +70,7 @@ class TsSegmenter { Status Flush(); const MuxerOptions& muxer_options_; + MuxerListener* const listener_; // Scale used to scale the input stream to TS's timesccale (which is 90000). // Used for calculating the duration in seconds fo the current segment. @@ -85,6 +91,12 @@ class TsSegmenter { bool ts_writer_file_opened_ = false; scoped_ptr pes_packet_generator_; + // For OnNewSegment(). + uint64_t current_segment_start_time_ = 0; + // Path of the current segment so that File::GetFileSize() can be used after + // the segment has been finalized. + std::string current_segment_path_; + DISALLOW_COPY_AND_ASSIGN(TsSegmenter); }; diff --git a/packager/media/formats/mp2t/ts_segmenter_unittest.cc b/packager/media/formats/mp2t/ts_segmenter_unittest.cc index 3b04677af9..07a29e3c42 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -10,6 +10,7 @@ #include "packager/media/base/audio_stream_info.h" #include "packager/media/base/test/status_test_util.h" #include "packager/media/base/video_stream_info.h" +#include "packager/media/event/mock_muxer_listener.h" #include "packager/media/formats/mp2t/ts_segmenter.h" namespace edash_packager { @@ -94,7 +95,7 @@ TEST_F(TsSegmenterTest, Initialize) { kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; - TsSegmenter segmenter(options); + TsSegmenter segmenter(options, nullptr); EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) @@ -115,7 +116,7 @@ TEST_F(TsSegmenterTest, AddSample) { MuxerOptions options; options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; - TsSegmenter segmenter(options); + TsSegmenter segmenter(options, nullptr); EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) @@ -173,7 +174,11 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { MuxerOptions options; options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; - TsSegmenter segmenter(options); + + MockMuxerListener mock_listener; + TsSegmenter segmenter(options, &mock_listener); + + const uint32_t kFirstPts = 1000; EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) @@ -191,6 +196,12 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { // Expect the segment to be finalized. sample1->set_duration(kInputTimescale * 11); + // (Finalize is not called at the end of this test so) Expect one segment + // event. The length should be the same as the above sample that exceeds the + // duration. + EXPECT_CALL(mock_listener, + OnNewSegment("file1.ts", kFirstPts, kTimeScale * 11, _)); + // Doesn't really matter how long this is. sample2->set_duration(kInputTimescale * 7); @@ -239,9 +250,11 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { // The pointers are released inside the segmenter. Sequence pes_packet_sequence; + PesPacket* first_pes = new PesPacket(); + first_pes->set_pts(kFirstPts); EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) .InSequence(pes_packet_sequence) - .WillOnce(Return(new PesPacket())); + .WillOnce(Return(first_pes)); EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) .InSequence(pes_packet_sequence) .WillOnce(Return(new PesPacket())); @@ -263,7 +276,7 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { MuxerOptions options; options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; - TsSegmenter segmenter(options); + TsSegmenter segmenter(options, nullptr); EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) @@ -292,7 +305,7 @@ TEST_F(TsSegmenterTest, Finalize) { MuxerOptions options; options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; - TsSegmenter segmenter(options); + TsSegmenter segmenter(options, nullptr); EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) @@ -321,7 +334,7 @@ TEST_F(TsSegmenterTest, SegmentOnlyBeforeKeyFrame) { MuxerOptions options; options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; - TsSegmenter segmenter(options); + TsSegmenter segmenter(options, nullptr); EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))