diff --git a/packager/app/packager_util.cc b/packager/app/packager_util.cc index 741a40c0c2..bedd61da83 100644 --- a/packager/app/packager_util.cc +++ b/packager/app/packager_util.cc @@ -182,14 +182,5 @@ MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params) { return mpd_options; } -Status ConnectHandlers(std::vector>& handlers) { - size_t num_handlers = handlers.size(); - Status status; - for (size_t i = 1; i < num_handlers; ++i) { - status.Update(handlers[i - 1]->AddHandler(handlers[i])); - } - return status; -} - } // namespace media } // namespace shaka diff --git a/packager/app/packager_util.h b/packager/app/packager_util.h index d6bff916f9..c5351545de 100644 --- a/packager/app/packager_util.h +++ b/packager/app/packager_util.h @@ -47,12 +47,6 @@ std::unique_ptr CreateDecryptionKeySource( /// @return MpdOptions from provided inputs. MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params); -/// Connect handlers in the vector. -/// @param handlers A vector of media handlers to be conncected. the handlers -/// are chained from front() to back(). -/// @return OK on success. -Status ConnectHandlers(std::vector>& handlers); - } // namespace media } // namespace shaka diff --git a/packager/media/replicator/replicator.cc b/packager/media/replicator/replicator.cc new file mode 100644 index 0000000000..dcd95b1ffc --- /dev/null +++ b/packager/media/replicator/replicator.cc @@ -0,0 +1,39 @@ +// Copyright 2017 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/replicator/replicator.h" + +namespace shaka { +namespace media { + +Status Replicator::InitializeInternal() { + return Status::OK; +} + +Status Replicator::Process(std::unique_ptr stream_data) { + Status status; + + for (auto& out : output_handlers()) { + std::unique_ptr copy(new StreamData(*stream_data)); + copy->stream_index = out.first; + + status.Update(Dispatch(std::move(copy))); + } + + return status; +} + +bool Replicator::ValidateOutputStreamIndex(size_t stream_index) const { + return true; +} + +Status Replicator::OnFlushRequest(size_t input_stream_index) { + DCHECK_EQ(input_stream_index, 0u); + return FlushAllDownstreams(); +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/replicator/replicator.gyp b/packager/media/replicator/replicator.gyp new file mode 100644 index 0000000000..6879102a9d --- /dev/null +++ b/packager/media/replicator/replicator.gyp @@ -0,0 +1,24 @@ +# Copyright 2017 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 + +{ + 'includes': [ + '../../common.gypi', + ], + 'targets': [ + { + 'target_name': 'replicator', + 'type': '<(component)', + 'sources': [ + 'replicator.cc', + 'replicator.h', + ], + 'dependencies': [ + '../base/media_base.gyp:media_base', + ], + }, + ], +} diff --git a/packager/media/replicator/replicator.h b/packager/media/replicator/replicator.h new file mode 100644 index 0000000000..21e64eaadb --- /dev/null +++ b/packager/media/replicator/replicator.h @@ -0,0 +1,30 @@ +// Copyright 2017 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_REPLICATOR_HANDLER_H_ +#define PACKAGER_MEDIA_REPLICATOR_HANDLER_H_ + +#include "packager/media/base/media_handler.h" + +namespace shaka { +namespace media { + +/// The replicator takes a single input and send the messages to multiple +/// downstream handlers. The messages that are sent downstream are not copies, +/// they are the original message. It is the responsibility of downstream +/// handlers to make a copy before modifying the message. +class Replicator : public MediaHandler { + private: + Status InitializeInternal() override; + Status Process(std::unique_ptr stream_data) override; + bool ValidateOutputStreamIndex(size_t stream_index) const override; + Status OnFlushRequest(size_t input_stream_index) override; +}; + +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_REPLICATOR_HANDLER_H_ diff --git a/packager/media/trick_play/trick_play_handler.cc b/packager/media/trick_play/trick_play_handler.cc index 106131188b..97752b2220 100644 --- a/packager/media/trick_play/trick_play_handler.cc +++ b/packager/media/trick_play/trick_play_handler.cc @@ -11,210 +11,180 @@ namespace shaka { namespace media { - namespace { -const size_t kMainStreamIndex = 0; -} +const size_t kStreamIndexIn = 0; +const size_t kStreamIndexOut = 0; +} // namespace -TrickPlayHandler::TrickPlayHandler() {} - -TrickPlayHandler::~TrickPlayHandler() {} - -void TrickPlayHandler::SetHandlerForMainStream( - std::shared_ptr handler) { - SetHandler(kMainStreamIndex, std::move(handler)); -} - -void TrickPlayHandler::SetHandlerForTrickPlay( - uint32_t trick_play_factor, - std::shared_ptr handler) { - trick_play_factors_.push_back(trick_play_factor); - // Trick play streams start from index 1. - SetHandler(trick_play_factors_.size(), std::move(handler)); +TrickPlayHandler::TrickPlayHandler(uint32_t factor) : factor_(factor) { + DCHECK_GE(factor, 1u) + << "Trick Play Handles must have a factor of 1 or higher."; } Status TrickPlayHandler::InitializeInternal() { - if (!HasMainStream()) { - return Status(error::TRICK_PLAY_ERROR, - "Trick play does not have main stream"); - } - if (trick_play_factors_.empty()) { - return Status(error::TRICK_PLAY_ERROR, - "Trick play factors are not specified."); - } - size_t num_trick_play_factors = trick_play_factors_.size(); - cached_stream_data_.resize(num_trick_play_factors); - playback_rates_.resize(num_trick_play_factors, 0); - return Status::OK; } Status TrickPlayHandler::Process(std::unique_ptr stream_data) { - // The non-trick play stream is dispatched at index 0. - // The trick-play streams are dispatched to index 1, index 2 and so on. DCHECK(stream_data); - DCHECK_EQ(stream_data->stream_index, 0u); + DCHECK_EQ(stream_data->stream_index, kStreamIndexIn); - std::unique_ptr copy(new StreamData); - *copy = *stream_data; - Status status = Dispatch(std::move(copy)); - if (!status.ok()) { - return status; + switch (stream_data->stream_data_type) { + case StreamDataType::kStreamInfo: + return OnStreamInfo(*stream_data->stream_info); + + case StreamDataType::kSegmentInfo: + return OnSegmentInfo(std::move(stream_data->segment_info)); + + case StreamDataType::kMediaSample: + return OnMediaSample(*stream_data->media_sample); + + default: + return Status(error::TRICK_PLAY_ERROR, + "Trick play only supports stream info, segment info, and " + "media sample messages."); } - - std::shared_ptr shared_stream_data(std::move(stream_data)); - - if (shared_stream_data->stream_data_type == StreamDataType::kStreamInfo) { - if (shared_stream_data->stream_info->stream_type() != kStreamVideo) { - status.SetError(error::TRICK_PLAY_ERROR, - "Trick play does not support non-video stream"); - return status; - } - const VideoStreamInfo& video_stream_info = - static_cast(*shared_stream_data->stream_info); - if (video_stream_info.trick_play_factor() > 0) { - status.SetError(error::TRICK_PLAY_ERROR, - "This stream is alreay a trick play stream."); - return status; - } - } - - if (shared_stream_data->stream_data_type == StreamDataType::kSegmentInfo) { - for (auto& cached_data : cached_stream_data_) { - // It is possible that trick play stream has large frame duration that - // some segments in the main stream are skipped. To avoid empty segments, - // only cache SegementInfo with MediaSample before it. - if (!cached_data.empty() && - cached_data.back()->stream_data_type == StreamDataType::kMediaSample) - cached_data.push_back(shared_stream_data); - } - return Status::OK; - } - - if (shared_stream_data->stream_data_type != StreamDataType::kMediaSample) { - // Non media sample stream data needs to be dispatched to every output - // stream. It is just cached in every queue until a new key frame comes or - // the stream is flushed. - for (size_t i = 0; i < cached_stream_data_.size(); ++i) - cached_stream_data_[i].push_back(shared_stream_data); - return Status::OK; - } - - if (shared_stream_data->media_sample->is_key_frame()) { - // For a new key frame, some of the trick play streams may include it. - // The cached data in those trick play streams will be processed. - DCHECK_EQ(trick_play_factors_.size(), cached_stream_data_.size()); - for (size_t i = 0; i < cached_stream_data_.size(); ++i) { - uint32_t factor = trick_play_factors_[i]; - if (total_key_frames_ % factor == 0) { - // Delay processing cached stream data until receiving the second key - // frame so that the GOP size could be derived. - if (!cached_stream_data_[i].empty() && total_key_frames_ > 0) { - // Num of frames between first two key frames in the trick play - // streams. Use this as the playback_rate. - if (playback_rates_[i] == 0) - playback_rates_[i] = total_frames_; - - Status status = - ProcessCachedStreamData(i + 1, &cached_stream_data_[i]); - if (!status.ok()) - return status; - } - cached_stream_data_[i].push_back(shared_stream_data); - } - } - - total_key_frames_++; - } - - total_frames_++; - prev_sample_end_timestamp_ = shared_stream_data->media_sample->dts() + - shared_stream_data->media_sample->duration(); - - return Status::OK; } -bool TrickPlayHandler::ValidateOutputStreamIndex(size_t stream_index) const { - // Output stream index should be less than the number of trick play - // streams + one original stream. - return stream_index <= trick_play_factors_.size(); -}; - Status TrickPlayHandler::OnFlushRequest(size_t input_stream_index) { - DCHECK_EQ(input_stream_index, 0u) - << "Trick Play Handler should only have single input."; - for (size_t i = 0; i < cached_stream_data_.size(); ++i) { - LOG_IF(WARNING, playback_rates_[i] == 0) - << "Max playout rate for trick play factor " << trick_play_factors_[i] - << " is not determined. " - << "Specify it as total number of frames: " << total_frames_ << "."; - playback_rates_[i] = total_frames_; - ProcessCachedStreamData(i + 1, &cached_stream_data_[i]); + DCHECK_EQ(input_stream_index, 0u); + + // Send everything out in its "as-is" state as we no longer need to update + // anything. + Status s; + while (s.ok() && delayed_messages_.size()) { + s.Update(Dispatch(std::move(delayed_messages_.front()))); + delayed_messages_.pop_front(); } - return MediaHandler::FlushAllDownstreams(); + + return s.ok() ? MediaHandler::FlushAllDownstreams() : s; } -bool TrickPlayHandler::HasMainStream() { - const auto& handlers = output_handlers(); - const auto& main_stream_handler = handlers.find(kMainStreamIndex); - if (main_stream_handler == handlers.end()) { - return false; +Status TrickPlayHandler::OnStreamInfo(const StreamInfo& info) { + if (info.stream_type() != kStreamVideo) { + return Status(error::TRICK_PLAY_ERROR, + "Trick play does not support non-video stream"); } - return main_stream_handler->second.first != nullptr; -} -Status TrickPlayHandler::ProcessCachedStreamData( - size_t output_stream_index, - std::deque>* cached_stream_data) { - while (!cached_stream_data->empty()) { - Status status = - ProcessOneStreamData(output_stream_index, *cached_stream_data->front()); - if (!status.ok()) { - return status; - } - cached_stream_data->pop_front(); + // Copy the video so we can edit it. Set play back rate to be zero. It will be + // updated later before being dispatched downstream. + video_info_ = std::make_shared( + static_cast(info)); + + if (video_info_->trick_play_factor() > 0) { + return Status(error::TRICK_PLAY_ERROR, + "This stream is already a trick play stream."); } + + video_info_->set_trick_play_factor(factor_); + video_info_->set_playback_rate(0); + + // Add video info to the message queue so that it can be sent out with all + // other messages. It won't be sent until the second trick play frame comes + // through. Until then, it can be updated via the |video_info_| member. + delayed_messages_.push_back( + StreamData::FromStreamInfo(kStreamIndexOut, video_info_)); + return Status::OK; } -Status TrickPlayHandler::ProcessOneStreamData(size_t output_stream_index, - const StreamData& stream_data) { - size_t trick_play_index = output_stream_index - 1; - uint32_t trick_play_factor = trick_play_factors_[trick_play_index]; - Status status; - switch (stream_data.stream_data_type) { - // trick_play_factor in StreamInfo should be modified. - case StreamDataType::kStreamInfo: { - const VideoStreamInfo& video_stream_info = - static_cast(*stream_data.stream_info); - std::shared_ptr trick_play_video_stream_info( - new VideoStreamInfo(video_stream_info)); - trick_play_video_stream_info->set_trick_play_factor(trick_play_factor); - DCHECK_GT(playback_rates_[trick_play_index], 0u); - trick_play_video_stream_info->set_playback_rate( - playback_rates_[trick_play_index]); - status = - DispatchStreamInfo(output_stream_index, trick_play_video_stream_info); - break; - } - case StreamDataType::kMediaSample: { - if (stream_data.media_sample->is_key_frame()) { - std::shared_ptr trick_play_media_sample = - MediaSample::CopyFrom(*(stream_data.media_sample)); - trick_play_media_sample->set_duration(prev_sample_end_timestamp_ - - stream_data.media_sample->dts()); - status = - DispatchMediaSample(output_stream_index, trick_play_media_sample); - } - break; - } - default: - std::unique_ptr copy(new StreamData(stream_data)); - copy->stream_index = output_stream_index; - status = Dispatch(std::move(copy)); - break; +Status TrickPlayHandler::OnSegmentInfo( + std::shared_ptr info) { + if (delayed_messages_.empty()) { + return Status(error::TRICK_PLAY_ERROR, + "Cannot handle segments with no preceding samples."); } - return status; + + // Trick play does not care about sub segments, only full segments matter. + if (info->is_subsegment) { + return Status::OK; + } + + const StreamDataType previous_type = + delayed_messages_.back()->stream_data_type; + + switch (previous_type) { + case StreamDataType::kSegmentInfo: + // In the case that there was an empty segment (no trick frame between in + // a segment) extend the previous segment to include the empty segment to + // avoid holes. + previous_segment_->duration += info->duration; + return Status::OK; + + case StreamDataType::kMediaSample: + // The segment has ended and there are media samples in the segment. + // Add the segment info to the list of delayed messages. Segment info will + // not get sent downstream until the next trick play frame comes through + // or flush is called. + previous_segment_ = std::make_shared(*info); + delayed_messages_.push_back( + StreamData::FromSegmentInfo(kStreamIndexOut, previous_segment_)); + return Status::OK; + + default: + return Status(error::TRICK_PLAY_ERROR, + "Unexpected sample in trick play deferred queue : type=" + + std::to_string(static_cast(previous_type))); + } +} + +Status TrickPlayHandler::OnMediaSample(const MediaSample& sample) { + total_frames_++; + + if (sample.is_key_frame()) { + total_key_frames_++; + + if ((total_key_frames_ - 1) % factor_ == 0) { + return OnTrickFrame(sample); + } + } + + // Update this now as it may be sent out soon via the delay message queue. + if (total_trick_frames_ < 2) { + // At this point, video_info will be at the head of the delay message queue + // and can still be updated safely. + + // The play back rate is determined by the number of frames between the + // first two trick play frames. The first trick play frame will be the + // first frame in the video. + video_info_->set_playback_rate(total_frames_); + } + + // If the frame is not a trick play frame, then take the duration of this + // frame and add it to the previous trick play frame so that it will span the + // gap created by not passing this frame through. + DCHECK(previous_trick_frame_); + previous_trick_frame_->set_duration(previous_trick_frame_->duration() + + sample.duration()); + + return Status::OK; +} + +Status TrickPlayHandler::OnTrickFrame(const MediaSample& sample) { + total_trick_frames_++; + + // Make a message we can store until later. + previous_trick_frame_ = MediaSample::CopyFrom(sample); + + // Add the message to our queue so that it will be ready to go out. + delayed_messages_.push_back( + StreamData::FromMediaSample(kStreamIndexOut, previous_trick_frame_)); + + // We need two trick play frames before we can send out our stream info, so we + // cannot send this media sample until after we send our sample info + // downstream. + if (total_trick_frames_ < 2) { + return Status::OK; + } + + // Send out all delayed messages up until the new trick play frame we just + // added. + Status s; + while (s.ok() && delayed_messages_.size() > 1) { + s.Update(Dispatch(std::move(delayed_messages_.front()))); + delayed_messages_.pop_front(); + } + return s; } } // namespace media diff --git a/packager/media/trick_play/trick_play_handler.h b/packager/media/trick_play/trick_play_handler.h index c9042cef6c..8caebec649 100644 --- a/packager/media/trick_play/trick_play_handler.h +++ b/packager/media/trick_play/trick_play_handler.h @@ -7,93 +7,65 @@ #ifndef PACKAGER_MEDIA_BASE_TRICK_PLAY_HANDLER_H_ #define PACKAGER_MEDIA_BASE_TRICK_PLAY_HANDLER_H_ +#include + #include "packager/media/base/media_handler.h" namespace shaka { namespace media { -/// TrickPlayHandler is a single-input-multiple-output media handler. It creates -/// trick play streams from the input. -// The stream data in trick play stream is not a simple duplicate. Some -// information need to be updated, including trick_play_factor in -// VideoStreamInfo, the duration in MediaSample (which makes sure there is no -// gap between the media sample dts). Since the duration information can be -// determined after getting the next media sample, a queue is used to cache the -// input stream data before the next key frame. +class VideoStreamInfo; + +/// TrickPlayHandler is a single-input single-output media handler. It takes +/// the input stream and converts it to a trick play stream by limiting which +/// samples get passed downstream. +// The stream data in trick play streams are not simple duplicates. Some +// information get changed (e.g. VideoStreamInfo.trick_play_factor). class TrickPlayHandler : public MediaHandler { public: - TrickPlayHandler(); - ~TrickPlayHandler() override; - - void SetHandlerForMainStream(std::shared_ptr handler); - void SetHandlerForTrickPlay(uint32_t trick_play_factor, - std::shared_ptr handler); - - protected: - /// @name MediaHandler implementation overrides. - /// @{ - Status InitializeInternal() override; - Status Process(std::unique_ptr stream_data) override; - bool ValidateOutputStreamIndex(size_t stream_index) const override; - Status OnFlushRequest(size_t input_stream_index) override; - /// @} + explicit TrickPlayHandler(uint32_t factor); private: - friend class TrickPlayHandlerTest; - - // Returns true if the trick play handler has main stream output handler - // connected, otherwise returns false. - bool HasMainStream(); - - // Process the cached stream data for one trick play stream. - // The cached data is dispatched to the |output_stream_index|. - Status ProcessCachedStreamData( - size_t output_stream_index, - std::deque>* cached_stream_data); - - // Process a single stream data. Depending on the stream data type, some - // information needs to be updated. - // Decoding timestamp for current key media sample. It is used for calculating - // the duration of previous key media sample, to make sure there is no gap - // between two key media samples. - Status ProcessOneStreamData( - size_t output_stream_index, - const StreamData& stream_data); - - // Trick play factors. Note that there can be multiple trick play factors, - // e.g., 2, 4 and 8. That means, one input video stream will generate 3 - // output trick play streams and original stream. Three trick play streams - // are: - // [key_frame_0, key_frame_2, key_frame_4, ...] - // [key_frame_0, key_frame_4, key_frame_8,...] - // [key_frame_0, key_frame_8, key_frame_16, ...]. - std::vector trick_play_factors_; - TrickPlayHandler(const TrickPlayHandler&) = delete; TrickPlayHandler& operator=(const TrickPlayHandler&) = delete; - /// Num of key frames received. - uint32_t total_key_frames_ = 0; + Status InitializeInternal() override; + Status Process(std::unique_ptr stream_data) override; + Status OnFlushRequest(size_t input_stream_index) override; - // Num of frames received. - uint32_t total_frames_ = 0; + Status OnStreamInfo(const StreamInfo& info); + Status OnSegmentInfo(std::shared_ptr info); + Status OnMediaSample(const MediaSample& sample); + Status OnTrickFrame(const MediaSample& sample); - // End timestamp of the previous processed media_sample, which is |dts| + - // |duration|. The duration of key frame in trick play stream is updated based - // on this timestamp. - int64_t prev_sample_end_timestamp_ = 0; + const uint32_t factor_; - // Record playback_rate for each trick play stream. - std::vector playback_rates_; + uint64_t total_frames_ = 0; + uint64_t total_key_frames_ = 0; + uint64_t total_trick_frames_ = 0; - // The data in output streams should be in the same order as in the input - // stream. Cache the stream data before next key frame so that we can - // determine the duration for the current key frame. Since one key frame may - // be dispatched to different trick play stream, each trick play stream need - // its own queue to handle the synchronization. - // TODO(hmchen): Use one queue and multiple iterators, instead of multiple - // queues. - std::vector>> cached_stream_data_; + // We cannot just send video info through as we need to calculate the play + // rate using the first two trick play frames. This reference should only be + // used to update the play back rate before video info is sent downstream. + // After getting sent downstream, this should never be used. + std::shared_ptr video_info_; + + // We need to track the segment that most recently finished so that we can + // extend its duration if there are empty segments. + std::shared_ptr previous_segment_; + + // Since we are dropping frames, the time that those frames would have been + // on screen need to be added to the frame before them. Keep a reference to + // the most recent trick play frame so that we can grow its duration as we + // drop other frames. + std::shared_ptr previous_trick_frame_; + + // Since we cannot send messages downstream right away, keep a queue of + // messages that need to be sent down. At the start, we use this to queue + // messages until we can send out |video_info_|. To ensure messages are + // kept in order, messages are only dispatched through this queue and never + // directly. + std::list> delayed_messages_; }; } // namespace media diff --git a/packager/media/trick_play/trick_play_handler_unittest.cc b/packager/media/trick_play/trick_play_handler_unittest.cc index bd3b2409ae..f13dcfeb6f 100644 --- a/packager/media/trick_play/trick_play_handler_unittest.cc +++ b/packager/media/trick_play/trick_play_handler_unittest.cc @@ -9,346 +9,274 @@ #include #include +#include "packager/media/base/audio_stream_info.h" #include "packager/media/base/media_handler_test_base.h" #include "packager/media/base/video_stream_info.h" #include "packager/status_test_util.h" -using ::testing::ElementsAre; -using ::testing::IsEmpty; - namespace shaka { namespace media { namespace { -const size_t kStreamIndex0 = 0; -const size_t kStreamIndex1 = 1; -const size_t kStreamIndex2 = 2; -const uint32_t kTimeScale = 800; -const uint32_t kDuration = 200; -const uint32_t kTrickPlayFactors[]{1, 2}; -const uint32_t kTrickPlayFactorsDecreasing[]{2, 1}; +const size_t kStreamIndex = 0; + +// This value does not matter as trick play does not use it, but it is needed +// to create the audio and video info. +const uint32_t kTimescale = 1000u; + +const bool kSubSegment = true; const bool kEncrypted = true; } // namespace -MATCHER_P5(IsTrickPlayVideoStreamInfo, - stream_index, - time_scale, - encrypted, - trick_play_factor, - playback_rate, - "") { - return arg->stream_index == stream_index && - arg->stream_data_type == StreamDataType::kStreamInfo && - arg->stream_info->time_scale() == time_scale && - arg->stream_info->is_encrypted() == encrypted && - arg->stream_info->stream_type() == kStreamVideo && - static_cast(arg->stream_info.get()) - ->trick_play_factor() == trick_play_factor && - static_cast(arg->stream_info.get()) - ->playback_rate() == playback_rate; +MATCHER_P2(IsTrickPlayVideoStream, trick_play_factor, playback_rate, "") { + if (arg->stream_data_type != StreamDataType::kStreamInfo || + arg->stream_info->stream_type() != kStreamVideo) { + return false; + } + const VideoStreamInfo* video_info = + static_cast(arg->stream_info.get()); + return video_info->trick_play_factor() == trick_play_factor && + video_info->playback_rate() == playback_rate; } -MATCHER_P3(IsKeyFrameMediaSample, stream_index, timestamp, duration, "") { - return arg->stream_index == stream_index && +MATCHER_P2(IsTrickPlaySample, timestamp, duration, "") { + return arg->stream_index == kStreamIndex && arg->stream_data_type == StreamDataType::kMediaSample && arg->media_sample->dts() == timestamp && arg->media_sample->duration() == duration && - arg->media_sample->is_key_frame() == true; + arg->media_sample->is_key_frame(); } -class TrickPlayHandlerTest : public MediaHandlerGraphTestBase { - public: - void SetUpTrickPlayHandler(const std::vector& trick_play_factors) { - trick_play_handler_.reset(new TrickPlayHandler()); - // Use SetUpGraph to set only input handler, and use - // SetHandlerForMainStream and SetHandlerForTrickPlay for the output - // handlers. - SetUpGraph(1, 0, trick_play_handler_); - trick_play_handler_->SetHandlerForMainStream(next_handler()); - for (uint32_t rate : trick_play_factors) { - trick_play_handler_->SetHandlerForTrickPlay(rate, next_handler()); - } - ASSERT_OK(trick_play_handler_->Initialize()); - } - - Status Process(std::unique_ptr stream_data) { - return trick_play_handler_->Process(std::move(stream_data)); - } - - Status FlushStream(size_t stream_index) { - return trick_play_handler_->OnFlushRequest(stream_index); - } - +// Creates a structure of [input]->[trick play]->[output] where all +// interactions are with input and output. +class TrickPlayHandlerTest : public MediaHandlerTestBase { protected: - std::shared_ptr trick_play_handler_; + void SetUpAndInitializeGraph(uint32_t factor) { + input_ = std::make_shared(); + trick_play_ = std::make_shared(factor); + output_ = std::make_shared(); + + ASSERT_OK(input_->AddHandler(trick_play_)); + ASSERT_OK(trick_play_->AddHandler(output_)); + + ASSERT_OK(input_->Initialize()); + } + + FakeInputMediaHandler* input() { return input_.get(); } + MockOutputMediaHandler* output() { return output_.get(); } + + // Create a series of samples where each sample has the same duration and ever + // sample that is an even multiple of |key_frame_frequency| will be a key + // frame. + std::vector> CreateSamples( + size_t count, + uint64_t start_time, + uint64_t frame_duration, + size_t key_frame_frequency) { + std::vector> samples; + + uint64_t time = start_time; + + for (size_t sample_index = 0; sample_index < count; sample_index++) { + const bool is_key_frame = (sample_index % key_frame_frequency) == 0; + samples.push_back(GetMediaSample(time, frame_duration, is_key_frame)); + time += frame_duration; + } + + return samples; + } + + private: + std::shared_ptr input_; + std::shared_ptr trick_play_; + std::shared_ptr output_; }; -// This test makes sure the audio stream is rejected by the trick play handler. -TEST_F(TrickPlayHandlerTest, AudioStream) { - const std::vector trick_play_factors(std::begin(kTrickPlayFactors), - std::end(kTrickPlayFactors)); - SetUpTrickPlayHandler(trick_play_factors); +// This test makes sure that audio streams are rejected by trick play handlers. +TEST_F(TrickPlayHandlerTest, RejectsAudio) { + const uint32_t kTrickPlayFactor = 1u; + SetUpAndInitializeGraph(kTrickPlayFactor); - Status status = Process(StreamData::FromStreamInfo( - kStreamIndex0, GetAudioStreamInfo(kTimeScale))); - Status kExpectStatus(error::TRICK_PLAY_ERROR, "Some Messages"); - EXPECT_TRUE(status.Matches(kExpectStatus)); + Status status = input()->Dispatch( + StreamData::FromStreamInfo(kStreamIndex, GetAudioStreamInfo(kTimescale))); + EXPECT_EQ(error::TRICK_PLAY_ERROR, status.error_code()); } -// This test makes sure the trick play handler can process stream data -// correctly. -TEST_F(TrickPlayHandlerTest, VideoStreamWithTrickPlay) { - const std::vector trick_play_factors(std::begin(kTrickPlayFactors), - std::end(kTrickPlayFactors)); - SetUpTrickPlayHandler(trick_play_factors); +// This test makes sure that when the trick play handler is initialized using +// a non-main-stream track that only a sub set of information gets passed +// through. This checks the specific case where no media samples are sent. +TEST_F(TrickPlayHandlerTest, TrickTrackNoSamples) { + // When there are no samples, play rate could not be determined and should be + // set to 0. + const int64_t kPlayRate = 0; + const uint32_t kTrickPlayFactor = 1u; - ASSERT_OK(Process(StreamData::FromStreamInfo( - kStreamIndex0, GetVideoStreamInfo(kTimeScale)))); - // The stream info is cached, so the output is empty. - EXPECT_THAT( - GetOutputStreamDataVector(), - ElementsAre(IsStreamInfo(kStreamIndex0, kTimeScale, !kEncrypted))); - ClearOutputStreamDataVector(); + SetUpAndInitializeGraph(kTrickPlayFactor); - const int kVideoStartTimestamp = 12345; - // Group of Picture size, the frequency of key frames. - const int kGOPSize = 3; - for (int i = 0; i < 3; ++i) { - const bool is_key_frame = (i % kGOPSize == 0); - ASSERT_OK(Process(StreamData::FromMediaSample( - kStreamIndex0, - GetMediaSample( - kVideoStartTimestamp + kDuration * i, - kDuration, - is_key_frame)))); + { + testing::InSequence s; + EXPECT_CALL(*output(), + OnProcess(IsTrickPlayVideoStream(kTrickPlayFactor, kPlayRate))); + EXPECT_CALL(*output(), OnFlush(kStreamIndex)); } - EXPECT_THAT( - GetOutputStreamDataVector(), - ElementsAre( - // Frame 0, key frame. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp, kDuration, - !kEncrypted), - // Frame 1. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration, - kDuration, !kEncrypted), - // Frame 2. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration * 2, - kDuration, !kEncrypted))); - ClearOutputStreamDataVector(); - - // This expectation are separated from the expectation above because - // ElementsAre supports at most 10 elements. - for (int i = 3; i < 6; ++i) { - const bool is_key_frame = (i % kGOPSize == 0); - ASSERT_OK(Process(StreamData::FromMediaSample( - kStreamIndex0, - GetMediaSample( - kVideoStartTimestamp + kDuration * i, - kDuration, - is_key_frame)))); - } - - EXPECT_THAT( - GetOutputStreamDataVector(), - ElementsAre( - // Frame 3, key frame. - IsKeyFrameMediaSample( - kStreamIndex0, kVideoStartTimestamp + kDuration * 3, kDuration), - // Stream info, TrickPlayFactor = 1. - IsTrickPlayVideoStreamInfo( - kStreamIndex1, kTimeScale, !kEncrypted, kTrickPlayFactors[0], - static_cast(kGOPSize) * kTrickPlayFactors[0]), - // Frame 0, TrickPlayFactor = 1. - IsKeyFrameMediaSample(kStreamIndex1, kVideoStartTimestamp, - kDuration * 3), - // Frame 4. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration * 4, - kDuration, !kEncrypted), - // Frame 5. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration * 5, - kDuration, !kEncrypted))); - ClearOutputStreamDataVector(); - - // This expectation are separated from the expectation above because - // ElementsAre supports at most 10 elements. - for (int i = 6; i < 8; ++i) { - const bool is_key_frame = (i % kGOPSize == 0); - ASSERT_OK(Process(StreamData::FromMediaSample( - kStreamIndex0, - GetMediaSample( - kVideoStartTimestamp + kDuration * i, - kDuration, - is_key_frame)))); - } - - EXPECT_THAT( - GetOutputStreamDataVector(), - ElementsAre( - // Frame 6, key frame. - IsKeyFrameMediaSample( - kStreamIndex0, kVideoStartTimestamp + kDuration * 6, kDuration), - // Frame 3, TrickPlayFactor = 1. - IsKeyFrameMediaSample(kStreamIndex1, - kVideoStartTimestamp + kDuration * 3, - kDuration * 3), - // Stream info, TrickPlayFactor = 2. - IsTrickPlayVideoStreamInfo( - kStreamIndex2, kTimeScale, !kEncrypted, kTrickPlayFactors[1], - static_cast(kGOPSize) * kTrickPlayFactors[1]), - // Frame 0, TrickPlayFactor = 2. - IsKeyFrameMediaSample(kStreamIndex2, kVideoStartTimestamp, - kDuration * 6), - // Frame 7. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration * 7, - kDuration, !kEncrypted))); - ClearOutputStreamDataVector(); - - ASSERT_OK(FlushStream(0)); - EXPECT_THAT(GetOutputStreamDataVector(), - ElementsAre( - // Frame 6, TrickPlayFactor = 1. - IsKeyFrameMediaSample(kStreamIndex1, - kVideoStartTimestamp + kDuration * 6, - kDuration * 2), - // Frame 6, TrickPlayFactor = 2. - IsKeyFrameMediaSample(kStreamIndex2, - kVideoStartTimestamp + kDuration * 6, - kDuration * 2))); - ClearOutputStreamDataVector(); - - // Flush again, get nothing. - ASSERT_OK(FlushStream(0)); - EXPECT_THAT(GetOutputStreamDataVector(), IsEmpty()); + ASSERT_OK(input()->Dispatch(StreamData::FromStreamInfo( + kStreamIndex, GetVideoStreamInfo(kTimescale)))); + ASSERT_OK(input()->FlushAllDownstreams()); } -// This test makes sure the trick play handler can process stream data -// correctly with a decreasing order of trick play factors. -TEST_F(TrickPlayHandlerTest, VideoStreamWithDecreasingTrickPlayFactors) { - const std::vector trick_play_factors( - std::begin(kTrickPlayFactorsDecreasing), - std::end(kTrickPlayFactorsDecreasing)); - SetUpTrickPlayHandler(trick_play_factors); +// This test makes sure that when the trick play handler is initialized using +// a non-main-stream track that only a sub set of information gets passed +// through. +TEST_F(TrickPlayHandlerTest, TrickTrackWithSamplesOnlyGetsKeyFrames) { + const uint32_t kTrickPlayFactor = 1u; + const int64_t kStartTime = 0; + const int64_t kDuration = 100; + const int64_t kKeyFrameRate = 3; - ASSERT_OK(Process(StreamData::FromStreamInfo( - kStreamIndex0, GetVideoStreamInfo(kTimeScale)))); - // The stream info is cached, so the output is empty. - EXPECT_THAT( - GetOutputStreamDataVector(), - ElementsAre(IsStreamInfo(kStreamIndex0, kTimeScale, !kEncrypted))); - ClearOutputStreamDataVector(); + const int64_t kPlayRate = kKeyFrameRate * kTrickPlayFactor; + const int64_t kTrickPlaySampleDuration = kDuration * kPlayRate; - const int kVideoStartTimestamp = 12345; - // Group of Picture size, the frequency of key frames. - const int kGOPSize = 3; - for (int i = 0; i < 3; ++i) { - const bool is_key_frame = (i % kGOPSize == 0); - ASSERT_OK(Process(StreamData::FromMediaSample( - kStreamIndex0, - GetMediaSample( - kVideoStartTimestamp + kDuration * i, - kDuration, - is_key_frame)))); + SetUpAndInitializeGraph(kTrickPlayFactor); + + // Only some samples we send to trick play should also be sent to our mock + // handler. + { + testing::InSequence s; + EXPECT_CALL(*output(), + OnProcess(IsTrickPlayVideoStream(kTrickPlayFactor, kPlayRate))); + for (int i = 0; i < 3; i++) { + EXPECT_CALL(*output(), OnProcess(IsTrickPlaySample( + kStartTime + i * kTrickPlaySampleDuration, + kTrickPlaySampleDuration))); + } + EXPECT_CALL(*output(), OnFlush(kStreamIndex)); } - EXPECT_THAT( - GetOutputStreamDataVector(), - ElementsAre( - // Frame 0, key frame. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp, kDuration, - !kEncrypted), - // Frame 1. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration, - kDuration, !kEncrypted), - // Frame 2. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration * 2, - kDuration, !kEncrypted))); - ClearOutputStreamDataVector(); + std::vector> samples = + CreateSamples(9 /* sample count */, kStartTime, kDuration, kKeyFrameRate); - // This expectation are separated from the expectation above because - // ElementsAre supports at most 10 elements. - for (int i = 3; i < 6; ++i) { - const bool is_key_frame = (i % kGOPSize == 0); - ASSERT_OK(Process(StreamData::FromMediaSample( - kStreamIndex0, - GetMediaSample( - kVideoStartTimestamp + kDuration * i, - kDuration, - is_key_frame)))); + ASSERT_OK(input()->Dispatch(StreamData::FromStreamInfo( + kStreamIndex, GetVideoStreamInfo(kTimescale)))); + + for (const auto& sample : samples) { + ASSERT_OK( + input()->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample))); } - EXPECT_THAT( - GetOutputStreamDataVector(), - ElementsAre( - // Frame 3, key frame. - IsKeyFrameMediaSample( - kStreamIndex0, kVideoStartTimestamp + kDuration * 3, kDuration), - // Stream info, TrickPlayFactor = 1. - IsTrickPlayVideoStreamInfo( - kStreamIndex2, kTimeScale, !kEncrypted, - kTrickPlayFactorsDecreasing[1], - static_cast(kGOPSize) * kTrickPlayFactorsDecreasing[1]), - // Frame 0, TrickPlayFactor = 1. - IsKeyFrameMediaSample(kStreamIndex2, kVideoStartTimestamp, - kDuration * 3), - // Frame 4. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration * 4, - kDuration, !kEncrypted), - // Frame 5. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration * 5, - kDuration, !kEncrypted))); - ClearOutputStreamDataVector(); + ASSERT_OK(input()->FlushAllDownstreams()); +} - // This expectation are separated from the expectation above because - // ElementsAre supports at most 10 elements. - for (int i = 6; i < 8; ++i) { - const bool is_key_frame = (i % kGOPSize == 0); - ASSERT_OK(Process(StreamData::FromMediaSample( - kStreamIndex0, - GetMediaSample( - kVideoStartTimestamp + kDuration * i, - kDuration, - is_key_frame)))); +// This test makes sure that when the trick play handler is initialized using +// a non-main-stream track that only a sub set of information gets passed +// through. +TEST_F(TrickPlayHandlerTest, TrickTrackWithSamples) { + const uint32_t kTrickPlayFactor = 2u; + const int64_t kStartTime = 0; + const int64_t kDuration = 100; + const int64_t kKeyFrameRate = 2; + + const int64_t kPlayRate = kKeyFrameRate * kTrickPlayFactor; + const int64_t kTrickPlaySampleDuration = kDuration * kPlayRate; + + SetUpAndInitializeGraph(kTrickPlayFactor); + + // Only some samples we send to trick play should also be sent to our mock + // handler. + { + testing::InSequence s; + EXPECT_CALL(*output(), + OnProcess(IsTrickPlayVideoStream(kTrickPlayFactor, kPlayRate))); + for (int i = 0; i < 2; i++) { + EXPECT_CALL(*output(), OnProcess(IsTrickPlaySample( + kStartTime + i * kTrickPlaySampleDuration, + kTrickPlaySampleDuration))); + } + EXPECT_CALL(*output(), OnFlush(0u)); } - EXPECT_THAT( - GetOutputStreamDataVector(), - ElementsAre( - // Frame 6, key frame. - IsKeyFrameMediaSample( - kStreamIndex0, kVideoStartTimestamp + kDuration * 6, kDuration), - // Stream info, TrickPlayFactor = 2. - IsTrickPlayVideoStreamInfo( - kStreamIndex1, kTimeScale, !kEncrypted, - kTrickPlayFactorsDecreasing[0], - static_cast(kGOPSize) * kTrickPlayFactorsDecreasing[0]), - // Frame 0, TrickPlayFactor = 2. - IsKeyFrameMediaSample(kStreamIndex1, kVideoStartTimestamp, - kDuration * 6), - // Frame 3, TrickPlayFactor = 1. - IsKeyFrameMediaSample(kStreamIndex2, - kVideoStartTimestamp + kDuration * 3, - kDuration * 3), - // Frame 7. - IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration * 7, - kDuration, !kEncrypted))); - ClearOutputStreamDataVector(); + std::vector> samples = + CreateSamples(8 /* sample count */, kStartTime, kDuration, kKeyFrameRate); - ASSERT_OK(FlushStream(0)); - EXPECT_THAT(GetOutputStreamDataVector(), - ElementsAre( - // Frame 6, TrickPlayFactor = 2. - IsKeyFrameMediaSample(kStreamIndex1, - kVideoStartTimestamp + kDuration * 6, - kDuration * 2), - // Frame 6, TrickPlayFactor = 1. - IsKeyFrameMediaSample(kStreamIndex2, - kVideoStartTimestamp + kDuration * 6, - kDuration * 2))); - ClearOutputStreamDataVector(); + ASSERT_OK(input()->Dispatch(StreamData::FromStreamInfo( + kStreamIndex, GetVideoStreamInfo(kTimescale)))); - // Flush again, get nothing. - ASSERT_OK(FlushStream(0)); - EXPECT_THAT(GetOutputStreamDataVector(), IsEmpty()); + for (const auto& sample : samples) { + ASSERT_OK( + input()->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample))); + } + + ASSERT_OK(input()->FlushAllDownstreams()); +} + +TEST_F(TrickPlayHandlerTest, TrickTrackWithSamplesAndSegments) { + const uint32_t kTrickPlayFactor = 1u; + const int64_t kStartTime = 0; + const int64_t kDuration = 100; + const int64_t kKeyFrameRate = 2; + + const int64_t kPlayRate = kKeyFrameRate * kTrickPlayFactor; + const int64_t kTrickPlaySampleDuration = kDuration * kPlayRate; + + SetUpAndInitializeGraph(kTrickPlayFactor); + + // Only some samples we send to trick play should also be sent to our mock + // handler. + { + testing::InSequence s; + EXPECT_CALL(*output(), + OnProcess(IsTrickPlayVideoStream(kTrickPlayFactor, kPlayRate))); + + // Segment One + for (int i = 0; i < 2; i++) { + EXPECT_CALL(*output(), OnProcess(IsTrickPlaySample( + kStartTime + kTrickPlaySampleDuration * i, + kTrickPlaySampleDuration))); + } + EXPECT_CALL(*output(), + OnProcess(IsSegmentInfo(kStreamIndex, kStartTime, 4 * kDuration, + !kSubSegment, !kEncrypted))); + + // Segment Two + for (int i = 2; i < 4; i++) { + EXPECT_CALL(*output(), OnProcess(IsTrickPlaySample( + kStartTime + kTrickPlaySampleDuration * i, + kTrickPlaySampleDuration))); + } + EXPECT_CALL(*output(), OnProcess(IsSegmentInfo( + kStreamIndex, kStartTime + 4 * kDuration, + 4 * kDuration, !kSubSegment, !kEncrypted))); + + EXPECT_CALL(*output(), OnFlush(0u)); + } + + std::vector> segment_one_samples = + CreateSamples(4 /* sample count */, kStartTime, kDuration, kKeyFrameRate); + + std::vector> segment_two_samples = + CreateSamples(4 /* sample count */, kStartTime + 4 * kDuration, kDuration, + kKeyFrameRate); + + ASSERT_OK(input()->Dispatch(StreamData::FromStreamInfo( + kStreamIndex, GetVideoStreamInfo(kTimescale)))); + + // Segment One + for (const auto& sample : segment_one_samples) { + ASSERT_OK( + input()->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample))); + } + ASSERT_OK(input()->Dispatch(StreamData::FromSegmentInfo( + kStreamIndex, GetSegmentInfo(kStartTime, kDuration * 4, !kEncrypted)))); + + // Segment Two + for (const auto& sample : segment_two_samples) { + ASSERT_OK( + input()->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample))); + } + ASSERT_OK(input()->Dispatch(StreamData::FromSegmentInfo( + kStreamIndex, + GetSegmentInfo(kStartTime + 4 * kDuration, 4 * kDuration, !kEncrypted)))); + + ASSERT_OK(input()->FlushAllDownstreams()); } } // namespace media diff --git a/packager/packager.cc b/packager/packager.cc index 6159f86cb1..1f14fe6e6d 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -34,6 +34,7 @@ #include "packager/media/formats/mp2t/ts_muxer.h" #include "packager/media/formats/mp4/mp4_muxer.h" #include "packager/media/formats/webm/webm_muxer.h" +#include "packager/media/replicator/replicator.h" #include "packager/media/trick_play/trick_play_handler.h" #include "packager/mpd/base/dash_iop_mpd_notifier.h" #include "packager/mpd/base/media_info.pb.h" @@ -211,11 +212,17 @@ class StreamDescriptorCompareFn { public: bool operator()(const StreamDescriptor& a, const StreamDescriptor& b) { if (a.input == b.input) { - if (a.stream_selector == b.stream_selector) - // Stream with high trick_play_factor is at the beginning. - return a.trick_play_factor > b.trick_play_factor; - else + if (a.stream_selector == b.stream_selector) { + // The MPD notifier requires that the main track comes first, so make + // sure that happens. + if (a.trick_play_factor == 0 || b.trick_play_factor == 0) { + return a.trick_play_factor == 0; + } else { + return a.trick_play_factor > b.trick_play_factor; + } + } else { return a.stream_selector < b.stream_selector; + } } return a.input < b.input; @@ -444,44 +451,46 @@ Status CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, DCHECK(!(mpd_notifier && hls_notifier)); DCHECK(jobs); + // Demuxers are shared among all streams with the same input. std::shared_ptr demuxer; - std::shared_ptr trick_play_handler; + std::shared_ptr replicator; std::string previous_input; - std::string previous_stream_selector; - int stream_number = 0; - for (StreamDescriptorList::const_iterator - stream_iter = stream_descriptors.begin(); - stream_iter != stream_descriptors.end(); - ++stream_iter, ++stream_number) { - MediaContainerName output_format = GetOutputFormat(*stream_iter); + std::string previous_selector; - // Process stream descriptor. - if (stream_iter->stream_selector == "text" && - output_format != CONTAINER_MOV) { + // Start at -1 so that it will be at 0 on the first iteration. + int stream_number = -1; + + // Move everything into a queue so that it will be easier to work with. + for (const StreamDescriptor& stream : stream_descriptors) { + stream_number += 1; + + if (stream.stream_selector == "text" && + GetOutputFormat(stream) != CONTAINER_MOV) { MediaInfo text_media_info; - if (!StreamInfoToTextMediaInfo(*stream_iter, &text_media_info)) { + if (!StreamInfoToTextMediaInfo(stream, &text_media_info)) { return Status(error::INVALID_ARGUMENT, "Could not create media info for stream."); } if (mpd_notifier) { uint32_t unused; - if (!mpd_notifier->NotifyNewContainer(text_media_info, &unused)) { - LOG(ERROR) << "Failed to process text file " << stream_iter->input; - } else { + if (mpd_notifier->NotifyNewContainer(text_media_info, &unused)) { mpd_notifier->Flush(); + } else { + return Status(error::PARSER_FAILURE, + "Failed to process text file " + stream.input); } } else if (packaging_params.output_media_info) { VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile( - text_media_info, stream_iter->output + kMediaInfoSuffix); + text_media_info, stream.output + kMediaInfoSuffix); } continue; } - if (stream_iter->input != previous_input) { - // New remux job needed. Create demux and job thread. - demuxer = std::make_shared(stream_iter->input); + // If we changed our input files, we need a new demuxer. + if (previous_input != stream.input) { + demuxer = std::make_shared(stream.input); demuxer->set_dump_stream_info( packaging_params.test_params.dump_stream_info); @@ -496,87 +505,88 @@ Status CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, } demuxer->SetKeySource(std::move(decryption_key_source)); } - jobs->emplace_back(new media::Job("RemuxJob", demuxer)); - trick_play_handler.reset(); - previous_input = stream_iter->input; - // Skip setting up muxers if output is not needed. - if (stream_iter->output.empty() && stream_iter->segment_template.empty()) - continue; - } - DCHECK(!jobs->empty()); - // Each stream selector requires an individual trick play handler. - // E.g., an input with two video streams needs two trick play handlers. - // TODO(hmchen): add a test case in packager_test.py for two video streams - // input. - if (stream_iter->stream_selector != previous_stream_selector) { - previous_stream_selector = stream_iter->stream_selector; - trick_play_handler.reset(); + jobs->emplace_back(new media::Job("RemuxJob", demuxer)); + } + + if (!stream.language.empty()) { + demuxer->SetLanguageOverride(stream.stream_selector, stream.language); + } + + const bool new_source = previous_input != stream.input || + previous_selector != stream.stream_selector; + previous_input = stream.input; + previous_selector = stream.stream_selector; + + // If the stream has no output, then there is no reason setting-up the rest + // of the pipeline. + if (stream.output.empty() && stream.segment_template.empty()) { + continue; + } + + if (new_source) { + replicator = std::make_shared(); + + std::shared_ptr chunker = + std::make_shared(packaging_params.chunking_params); + + std::shared_ptr encryptor = CreateEncryptionHandler( + packaging_params, stream, encryption_key_source); + + Status status; + + // The path is different if there is encryption. Even though there are + // common elements, it is easier to understand the path if they are + // expressed independently of each other. + if (encryptor) { + status.Update(demuxer->SetHandler(stream.stream_selector, chunker)); + status.Update(chunker->AddHandler(encryptor)); + status.Update(encryptor->AddHandler(replicator)); + } else { + status.Update(demuxer->SetHandler(stream.stream_selector, chunker)); + status.Update(chunker->AddHandler(replicator)); + } + + if (!status.ok()) { + return status; + } + + if (!stream.language.empty()) { + demuxer->SetLanguageOverride(stream.stream_selector, stream.language); + } } // Create the muxer (output) for this track. std::unique_ptr muxer_listener = CreateMuxerListener( - *stream_iter, stream_number, packaging_params.output_media_info, - mpd_notifier, hls_notifier); + stream, stream_number, packaging_params.output_media_info, mpd_notifier, + hls_notifier); std::shared_ptr muxer = CreateMuxer( - packaging_params, *stream_iter, + packaging_params, stream, packaging_params.test_params.inject_fake_clock ? fake_clock : nullptr, std::move(muxer_listener)); if (!muxer) { return Status(error::INVALID_ARGUMENT, "Failed to create muxer for " + - stream_iter->input + ":" + - stream_iter->stream_selector); + stream.input + ":" + + stream.stream_selector); } - // Create a new trick_play_handler. Note that the stream_decriptors - // are sorted so that for the same input and stream_selector, the main - // stream is always the last one following the trick play streams. - if (stream_iter->trick_play_factor > 0) { - if (!trick_play_handler) { - trick_play_handler.reset(new TrickPlayHandler()); - } - trick_play_handler->SetHandlerForTrickPlay(stream_iter->trick_play_factor, - std::move(muxer)); - if (trick_play_handler->IsConnected()) - continue; - } else if (trick_play_handler) { - trick_play_handler->SetHandlerForMainStream(std::move(muxer)); - DCHECK(trick_play_handler->IsConnected()); - continue; - } - - std::vector> handlers; - - auto chunking_handler = - std::make_shared(packaging_params.chunking_params); - handlers.push_back(chunking_handler); - - std::shared_ptr encryption_handler = CreateEncryptionHandler( - packaging_params, *stream_iter, encryption_key_source); - if (encryption_handler) { - handlers.push_back(encryption_handler); - } - - // If trick_play_handler is available, muxer should already be connected to - // trick_play_handler. - if (trick_play_handler) { - handlers.push_back(trick_play_handler); - } else { - handlers.push_back(std::move(muxer)); + std::shared_ptr trick_play; + if (stream.trick_play_factor) { + trick_play = std::make_shared(stream.trick_play_factor); } Status status; - status.Update( - demuxer->SetHandler(stream_iter->stream_selector, chunking_handler)); - status.Update(ConnectHandlers(handlers)); + if (trick_play) { + status.Update(replicator->AddHandler(trick_play)); + status.Update(trick_play->AddHandler(muxer)); + } else { + status.Update(replicator->AddHandler(muxer)); + } if (!status.ok()) { return status; } - if (!stream_iter->language.empty()) - demuxer->SetLanguageOverride(stream_iter->stream_selector, - stream_iter->language); } // Initialize processing graph. diff --git a/packager/packager.gyp b/packager/packager.gyp index 68713dc4f9..326347ac54 100644 --- a/packager/packager.gyp +++ b/packager/packager.gyp @@ -24,8 +24,8 @@ 'dependencies': [ 'file/file.gyp:file', 'hls/hls.gyp:hls_builder', - 'media/codecs/codecs.gyp:codecs', 'media/chunking/chunking.gyp:chunking', + 'media/codecs/codecs.gyp:codecs', 'media/demuxer/demuxer.gyp:demuxer', 'media/event/media_event.gyp:media_event', 'media/formats/mp2t/mp2t.gyp:mp2t', @@ -34,6 +34,7 @@ 'media/formats/webm/webm.gyp:webm', 'media/formats/webvtt/webvtt.gyp:webvtt', 'media/formats/wvm/wvm.gyp:wvm', + 'media/replicator/replicator.gyp:replicator', 'media/trick_play/trick_play.gyp:trick_play', 'mpd/mpd.gyp:mpd_builder', 'third_party/boringssl/boringssl.gyp:boringssl',