Use MuxerListener in TsMuxer

- MuxerListener is used in TsMuxer so that a listener can be used to
  e.g. generate manifests.

Change-Id: I11c745e1c2b71d5ec901387fe42713d4ad69dc03
This commit is contained in:
Rintaro Kuroiwa 2016-04-16 15:58:47 -07:00
parent 13202f91b6
commit f3ed07a64e
9 changed files with 172 additions and 15 deletions

View File

@ -32,6 +32,18 @@
'../filters/filters.gyp:filters', '../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', 'target_name': 'media_event_unittest',
'type': '<(gtest_target_type)', 'type': '<(gtest_target_type)',

View File

@ -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

View File

@ -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 <gmock/gmock.h>
#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<uint8_t>& key_id,
const std::vector<uint8_t>& iv,
const std::vector<ProtectionSystemSpecificInfo>& 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_

View File

@ -64,6 +64,7 @@
'dependencies': [ 'dependencies': [
'../../../testing/gtest.gyp:gtest', '../../../testing/gtest.gyp:gtest',
'../../../testing/gmock.gyp:gmock', '../../../testing/gmock.gyp:gmock',
'../../event/media_event.gyp:mock_muxer_listener',
'../../filters/filters.gyp:filters', '../../filters/filters.gyp:filters',
'../../test/media_test.gyp:media_test_support', '../../test/media_test.gyp:media_test_support',
'../mpeg/mpeg.gyp:mpeg', '../mpeg/mpeg.gyp:mpeg',

View File

@ -10,23 +10,50 @@ namespace edash_packager {
namespace media { namespace media {
namespace mp2t { namespace mp2t {
TsMuxer::TsMuxer(const MuxerOptions& muxer_options) namespace {
: Muxer(muxer_options), segmenter_(options()) {} const uint32_t kTsTimescale = 90000;
} // namespace
TsMuxer::TsMuxer(const MuxerOptions& muxer_options) : Muxer(muxer_options) {}
TsMuxer::~TsMuxer() {} TsMuxer::~TsMuxer() {}
Status TsMuxer::Initialize() { Status TsMuxer::Initialize() {
if (streams().size() > 1u) if (streams().size() > 1u)
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams."); 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() { Status TsMuxer::Finalize() {
return segmenter_.Finalize(); FireOnMediaEndEvent();
return segmenter_->Finalize();
} }
Status TsMuxer::DoAddSample(const MediaStream* stream, Status TsMuxer::DoAddSample(const MediaStream* stream,
scoped_refptr<MediaSample> sample) { scoped_refptr<MediaSample> 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 } // namespace mp2t

View File

@ -29,7 +29,10 @@ class TsMuxer : public Muxer {
Status DoAddSample(const MediaStream* stream, Status DoAddSample(const MediaStream* stream,
scoped_refptr<MediaSample> sample) override; scoped_refptr<MediaSample> sample) override;
TsSegmenter segmenter_; void FireOnMediaStartEvent();
void FireOnMediaEndEvent();
scoped_ptr<TsSegmenter> segmenter_;
DISALLOW_COPY_AND_ASSIGN(TsMuxer); DISALLOW_COPY_AND_ASSIGN(TsMuxer);
}; };

View File

@ -10,6 +10,7 @@
#include "packager/media/base/muxer_util.h" #include "packager/media/base/muxer_util.h"
#include "packager/media/base/status.h" #include "packager/media/base/status.h"
#include "packager/media/event/muxer_listener.h"
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
@ -19,8 +20,9 @@ namespace {
const double kTsTimescale = 90000; const double kTsTimescale = 90000;
} // namespace } // namespace
TsSegmenter::TsSegmenter(const MuxerOptions& options) TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener)
: muxer_options_(options), : muxer_options_(options),
listener_(listener),
ts_writer_(new TsWriter()), ts_writer_(new TsWriter()),
pes_packet_generator_(new PesPacketGenerator()) {} pes_packet_generator_(new PesPacketGenerator()) {}
TsSegmenter::~TsSegmenter() {} TsSegmenter::~TsSegmenter() {}
@ -91,6 +93,8 @@ Status TsSegmenter::OpenNewSegmentIfClosed(uint32_t next_pts) {
segment_number_++, muxer_options_.bandwidth); segment_number_++, muxer_options_.bandwidth);
if (!ts_writer_->NewSegment(segment_name)) if (!ts_writer_->NewSegment(segment_name))
return Status(error::MUXER_FAILURE, "Failed to initilize TsPacketWriter."); 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; ts_writer_file_opened_ = true;
return Status::OK; return Status::OK;
} }
@ -125,9 +129,18 @@ Status TsSegmenter::Flush() {
if (!ts_writer_->FinalizeSegment()) { if (!ts_writer_->FinalizeSegment()) {
return Status(error::MUXER_FAILURE, "Failed to finalize TsWriter."); 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; ts_writer_file_opened_ = false;
} }
current_segment_total_sample_duration_ = 0.0; current_segment_total_sample_duration_ = 0.0;
current_segment_start_time_ = 0;
current_segment_path_.clear();
return Status::OK; return Status::OK;
} }

View File

@ -17,6 +17,9 @@
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
class MuxerListener;
namespace mp2t { namespace mp2t {
// TODO(rkuroiwa): For now, this implements multifile segmenter. Like other // TODO(rkuroiwa): For now, this implements multifile segmenter. Like other
@ -26,7 +29,9 @@ class TsSegmenter {
public: public:
/// @param options is the options for this muxer. This must stay valid /// @param options is the options for this muxer. This must stay valid
/// throughout the life time of the instance. /// 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(); ~TsSegmenter();
/// Initialize the object. /// Initialize the object.
@ -65,6 +70,7 @@ class TsSegmenter {
Status Flush(); Status Flush();
const MuxerOptions& muxer_options_; const MuxerOptions& muxer_options_;
MuxerListener* const listener_;
// Scale used to scale the input stream to TS's timesccale (which is 90000). // 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. // Used for calculating the duration in seconds fo the current segment.
@ -85,6 +91,12 @@ class TsSegmenter {
bool ts_writer_file_opened_ = false; bool ts_writer_file_opened_ = false;
scoped_ptr<PesPacketGenerator> pes_packet_generator_; scoped_ptr<PesPacketGenerator> 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); DISALLOW_COPY_AND_ASSIGN(TsSegmenter);
}; };

View File

@ -10,6 +10,7 @@
#include "packager/media/base/audio_stream_info.h" #include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/test/status_test_util.h" #include "packager/media/base/test/status_test_util.h"
#include "packager/media/base/video_stream_info.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" #include "packager/media/formats/mp2t/ts_segmenter.h"
namespace edash_packager { namespace edash_packager {
@ -94,7 +95,7 @@ TEST_F(TsSegmenterTest, Initialize) {
kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted));
MuxerOptions options; MuxerOptions options;
options.segment_template = "file$Number$.ts"; 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_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
@ -115,7 +116,7 @@ TEST_F(TsSegmenterTest, AddSample) {
MuxerOptions options; MuxerOptions options;
options.segment_duration = 10.0; options.segment_duration = 10.0;
options.segment_template = "file$Number$.ts"; 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_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
@ -173,7 +174,11 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
MuxerOptions options; MuxerOptions options;
options.segment_duration = 10.0; options.segment_duration = 10.0;
options.segment_template = "file$Number$.ts"; 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_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
@ -191,6 +196,12 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
// Expect the segment to be finalized. // Expect the segment to be finalized.
sample1->set_duration(kInputTimescale * 11); 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. // Doesn't really matter how long this is.
sample2->set_duration(kInputTimescale * 7); sample2->set_duration(kInputTimescale * 7);
@ -239,9 +250,11 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
// The pointers are released inside the segmenter. // The pointers are released inside the segmenter.
Sequence pes_packet_sequence; Sequence pes_packet_sequence;
PesPacket* first_pes = new PesPacket();
first_pes->set_pts(kFirstPts);
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
.InSequence(pes_packet_sequence) .InSequence(pes_packet_sequence)
.WillOnce(Return(new PesPacket())); .WillOnce(Return(first_pes));
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
.InSequence(pes_packet_sequence) .InSequence(pes_packet_sequence)
.WillOnce(Return(new PesPacket())); .WillOnce(Return(new PesPacket()));
@ -263,7 +276,7 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) {
MuxerOptions options; MuxerOptions options;
options.segment_duration = 10.0; options.segment_duration = 10.0;
options.segment_template = "file$Number$.ts"; 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_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
@ -292,7 +305,7 @@ TEST_F(TsSegmenterTest, Finalize) {
MuxerOptions options; MuxerOptions options;
options.segment_duration = 10.0; options.segment_duration = 10.0;
options.segment_template = "file$Number$.ts"; 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_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
@ -321,7 +334,7 @@ TEST_F(TsSegmenterTest, SegmentOnlyBeforeKeyFrame) {
MuxerOptions options; MuxerOptions options;
options.segment_duration = 10.0; options.segment_duration = 10.0;
options.segment_template = "file$Number$.ts"; 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_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))