From 8067fd48186bc7e18bad6f151852bb586e1b854a Mon Sep 17 00:00:00 2001 From: Aaron Vaage Date: Mon, 28 Aug 2017 15:27:05 -0700 Subject: [PATCH] Made Trick Play A 1:1 Handler Before the trick play handler would have one input and multiple outputs. The normal media handler API for adding handlers had to be ignored when setting-up the trick play handler. This changes the trick play handler to have one input and one output. It uses the standard AddHandler method calls. When initialized, the trick play factor must be provided. This included a run of 'clang-format' over all edited files. Change-Id: I7b3cdf0a2472e2f89ab194867c4b984e26f18f24 --- packager/app/packager_util.cc | 9 - packager/app/packager_util.h | 6 - packager/media/replicator/replicator.cc | 39 ++ packager/media/replicator/replicator.gyp | 24 + packager/media/replicator/replicator.h | 30 + .../media/trick_play/trick_play_handler.cc | 328 +++++------ .../media/trick_play/trick_play_handler.h | 114 ++-- .../trick_play/trick_play_handler_unittest.cc | 526 ++++++++---------- packager/packager.cc | 178 +++--- packager/packager.gyp | 3 +- 10 files changed, 608 insertions(+), 649 deletions(-) create mode 100644 packager/media/replicator/replicator.cc create mode 100644 packager/media/replicator/replicator.gyp create mode 100644 packager/media/replicator/replicator.h 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',