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
This commit is contained in:
Aaron Vaage 2017-08-28 15:27:05 -07:00
parent 31bf6dab59
commit 8067fd4818
10 changed files with 608 additions and 649 deletions

View File

@ -182,14 +182,5 @@ MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params) {
return mpd_options; return mpd_options;
} }
Status ConnectHandlers(std::vector<std::shared_ptr<MediaHandler>>& 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 media
} // namespace shaka } // namespace shaka

View File

@ -47,12 +47,6 @@ std::unique_ptr<KeySource> CreateDecryptionKeySource(
/// @return MpdOptions from provided inputs. /// @return MpdOptions from provided inputs.
MpdOptions GetMpdOptions(bool on_demand_profile, const MpdParams& mpd_params); 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<std::shared_ptr<MediaHandler>>& handlers);
} // namespace media } // namespace media
} // namespace shaka } // namespace shaka

View File

@ -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<StreamData> stream_data) {
Status status;
for (auto& out : output_handlers()) {
std::unique_ptr<StreamData> 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

View File

@ -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',
],
},
],
}

View File

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

View File

@ -11,210 +11,180 @@
namespace shaka { namespace shaka {
namespace media { namespace media {
namespace { namespace {
const size_t kMainStreamIndex = 0; const size_t kStreamIndexIn = 0;
} const size_t kStreamIndexOut = 0;
} // namespace
TrickPlayHandler::TrickPlayHandler() {} TrickPlayHandler::TrickPlayHandler(uint32_t factor) : factor_(factor) {
DCHECK_GE(factor, 1u)
TrickPlayHandler::~TrickPlayHandler() {} << "Trick Play Handles must have a factor of 1 or higher.";
void TrickPlayHandler::SetHandlerForMainStream(
std::shared_ptr<MediaHandler> handler) {
SetHandler(kMainStreamIndex, std::move(handler));
}
void TrickPlayHandler::SetHandlerForTrickPlay(
uint32_t trick_play_factor,
std::shared_ptr<MediaHandler> handler) {
trick_play_factors_.push_back(trick_play_factor);
// Trick play streams start from index 1.
SetHandler(trick_play_factors_.size(), std::move(handler));
} }
Status TrickPlayHandler::InitializeInternal() { 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; return Status::OK;
} }
Status TrickPlayHandler::Process(std::unique_ptr<StreamData> stream_data) { Status TrickPlayHandler::Process(std::unique_ptr<StreamData> 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(stream_data);
DCHECK_EQ(stream_data->stream_index, 0u); DCHECK_EQ(stream_data->stream_index, kStreamIndexIn);
std::unique_ptr<StreamData> copy(new StreamData); switch (stream_data->stream_data_type) {
*copy = *stream_data; case StreamDataType::kStreamInfo:
Status status = Dispatch(std::move(copy)); return OnStreamInfo(*stream_data->stream_info);
if (!status.ok()) {
return status;
}
std::shared_ptr<StreamData> shared_stream_data(std::move(stream_data)); case StreamDataType::kSegmentInfo:
return OnSegmentInfo(std::move(stream_data->segment_info));
if (shared_stream_data->stream_data_type == StreamDataType::kStreamInfo) { case StreamDataType::kMediaSample:
if (shared_stream_data->stream_info->stream_type() != kStreamVideo) { return OnMediaSample(*stream_data->media_sample);
status.SetError(error::TRICK_PLAY_ERROR,
"Trick play does not support non-video stream");
return status;
}
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(*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) { default:
for (auto& cached_data : cached_stream_data_) { return Status(error::TRICK_PLAY_ERROR,
// It is possible that trick play stream has large frame duration that "Trick play only supports stream info, segment info, and "
// some segments in the main stream are skipped. To avoid empty segments, "media sample messages.");
// 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) { Status TrickPlayHandler::OnFlushRequest(size_t input_stream_index) {
DCHECK_EQ(input_stream_index, 0u) 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) { // Send everything out in its "as-is" state as we no longer need to update
LOG_IF(WARNING, playback_rates_[i] == 0) // anything.
<< "Max playout rate for trick play factor " << trick_play_factors_[i] Status s;
<< " is not determined. " while (s.ok() && delayed_messages_.size()) {
<< "Specify it as total number of frames: " << total_frames_ << "."; s.Update(Dispatch(std::move(delayed_messages_.front())));
playback_rates_[i] = total_frames_; delayed_messages_.pop_front();
ProcessCachedStreamData(i + 1, &cached_stream_data_[i]);
}
return MediaHandler::FlushAllDownstreams();
} }
bool TrickPlayHandler::HasMainStream() { return s.ok() ? MediaHandler::FlushAllDownstreams() : s;
const auto& handlers = output_handlers();
const auto& main_stream_handler = handlers.find(kMainStreamIndex);
if (main_stream_handler == handlers.end()) {
return false;
}
return main_stream_handler->second.first != nullptr;
} }
Status TrickPlayHandler::ProcessCachedStreamData( Status TrickPlayHandler::OnStreamInfo(const StreamInfo& info) {
size_t output_stream_index, if (info.stream_type() != kStreamVideo) {
std::deque<std::shared_ptr<StreamData>>* cached_stream_data) { return Status(error::TRICK_PLAY_ERROR,
while (!cached_stream_data->empty()) { "Trick play does not support non-video stream");
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<VideoStreamInfo>(
static_cast<const VideoStreamInfo&>(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; return Status::OK;
} }
Status TrickPlayHandler::ProcessOneStreamData(size_t output_stream_index, Status TrickPlayHandler::OnSegmentInfo(
const StreamData& stream_data) { std::shared_ptr<const SegmentInfo> info) {
size_t trick_play_index = output_stream_index - 1; if (delayed_messages_.empty()) {
uint32_t trick_play_factor = trick_play_factors_[trick_play_index]; return Status(error::TRICK_PLAY_ERROR,
Status status; "Cannot handle segments with no preceding samples.");
switch (stream_data.stream_data_type) {
// trick_play_factor in StreamInfo should be modified.
case StreamDataType::kStreamInfo: {
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(*stream_data.stream_info);
std::shared_ptr<VideoStreamInfo> 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()) { // Trick play does not care about sub segments, only full segments matter.
std::shared_ptr<MediaSample> trick_play_media_sample = if (info->is_subsegment) {
MediaSample::CopyFrom(*(stream_data.media_sample)); return Status::OK;
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;
} }
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<SegmentInfo>(*info);
delayed_messages_.push_back(
StreamData::FromSegmentInfo(kStreamIndexOut, previous_segment_));
return Status::OK;
default: default:
std::unique_ptr<StreamData> copy(new StreamData(stream_data)); return Status(error::TRICK_PLAY_ERROR,
copy->stream_index = output_stream_index; "Unexpected sample in trick play deferred queue : type=" +
status = Dispatch(std::move(copy)); std::to_string(static_cast<int>(previous_type)));
break;
} }
return status; }
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 } // namespace media

View File

@ -7,93 +7,65 @@
#ifndef PACKAGER_MEDIA_BASE_TRICK_PLAY_HANDLER_H_ #ifndef PACKAGER_MEDIA_BASE_TRICK_PLAY_HANDLER_H_
#define PACKAGER_MEDIA_BASE_TRICK_PLAY_HANDLER_H_ #define PACKAGER_MEDIA_BASE_TRICK_PLAY_HANDLER_H_
#include <list>
#include "packager/media/base/media_handler.h" #include "packager/media/base/media_handler.h"
namespace shaka { namespace shaka {
namespace media { namespace media {
/// TrickPlayHandler is a single-input-multiple-output media handler. It creates class VideoStreamInfo;
/// trick play streams from the input.
// The stream data in trick play stream is not a simple duplicate. Some /// TrickPlayHandler is a single-input single-output media handler. It takes
// information need to be updated, including trick_play_factor in /// the input stream and converts it to a trick play stream by limiting which
// VideoStreamInfo, the duration in MediaSample (which makes sure there is no /// samples get passed downstream.
// gap between the media sample dts). Since the duration information can be // The stream data in trick play streams are not simple duplicates. Some
// determined after getting the next media sample, a queue is used to cache the // information get changed (e.g. VideoStreamInfo.trick_play_factor).
// input stream data before the next key frame.
class TrickPlayHandler : public MediaHandler { class TrickPlayHandler : public MediaHandler {
public: public:
TrickPlayHandler(); explicit TrickPlayHandler(uint32_t factor);
~TrickPlayHandler() override;
void SetHandlerForMainStream(std::shared_ptr<MediaHandler> handler);
void SetHandlerForTrickPlay(uint32_t trick_play_factor,
std::shared_ptr<MediaHandler> handler);
protected:
/// @name MediaHandler implementation overrides.
/// @{
Status InitializeInternal() override;
Status Process(std::unique_ptr<StreamData> stream_data) override;
bool ValidateOutputStreamIndex(size_t stream_index) const override;
Status OnFlushRequest(size_t input_stream_index) override;
/// @}
private: 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<std::shared_ptr<StreamData>>* 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<uint32_t> trick_play_factors_;
TrickPlayHandler(const TrickPlayHandler&) = delete; TrickPlayHandler(const TrickPlayHandler&) = delete;
TrickPlayHandler& operator=(const TrickPlayHandler&) = delete; TrickPlayHandler& operator=(const TrickPlayHandler&) = delete;
/// Num of key frames received. Status InitializeInternal() override;
uint32_t total_key_frames_ = 0; Status Process(std::unique_ptr<StreamData> stream_data) override;
Status OnFlushRequest(size_t input_stream_index) override;
// Num of frames received. Status OnStreamInfo(const StreamInfo& info);
uint32_t total_frames_ = 0; Status OnSegmentInfo(std::shared_ptr<const SegmentInfo> info);
Status OnMediaSample(const MediaSample& sample);
Status OnTrickFrame(const MediaSample& sample);
// End timestamp of the previous processed media_sample, which is |dts| + const uint32_t factor_;
// |duration|. The duration of key frame in trick play stream is updated based
// on this timestamp.
int64_t prev_sample_end_timestamp_ = 0;
// Record playback_rate for each trick play stream. uint64_t total_frames_ = 0;
std::vector<uint32_t> playback_rates_; 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 // We cannot just send video info through as we need to calculate the play
// stream. Cache the stream data before next key frame so that we can // rate using the first two trick play frames. This reference should only be
// determine the duration for the current key frame. Since one key frame may // used to update the play back rate before video info is sent downstream.
// be dispatched to different trick play stream, each trick play stream need // After getting sent downstream, this should never be used.
// its own queue to handle the synchronization. std::shared_ptr<VideoStreamInfo> video_info_;
// TODO(hmchen): Use one queue and multiple iterators, instead of multiple
// queues. // We need to track the segment that most recently finished so that we can
std::vector<std::deque<std::shared_ptr<StreamData>>> cached_stream_data_; // extend its duration if there are empty segments.
std::shared_ptr<SegmentInfo> 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<MediaSample> 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<std::unique_ptr<StreamData>> delayed_messages_;
}; };
} // namespace media } // namespace media

View File

@ -9,346 +9,274 @@
#include <gmock/gmock.h> #include <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/media_handler_test_base.h" #include "packager/media/base/media_handler_test_base.h"
#include "packager/media/base/video_stream_info.h" #include "packager/media/base/video_stream_info.h"
#include "packager/status_test_util.h" #include "packager/status_test_util.h"
using ::testing::ElementsAre;
using ::testing::IsEmpty;
namespace shaka { namespace shaka {
namespace media { namespace media {
namespace { namespace {
const size_t kStreamIndex0 = 0; const size_t kStreamIndex = 0;
const size_t kStreamIndex1 = 1;
const size_t kStreamIndex2 = 2; // This value does not matter as trick play does not use it, but it is needed
const uint32_t kTimeScale = 800; // to create the audio and video info.
const uint32_t kDuration = 200; const uint32_t kTimescale = 1000u;
const uint32_t kTrickPlayFactors[]{1, 2};
const uint32_t kTrickPlayFactorsDecreasing[]{2, 1}; const bool kSubSegment = true;
const bool kEncrypted = true; const bool kEncrypted = true;
} // namespace } // namespace
MATCHER_P5(IsTrickPlayVideoStreamInfo, MATCHER_P2(IsTrickPlayVideoStream, trick_play_factor, playback_rate, "") {
stream_index, if (arg->stream_data_type != StreamDataType::kStreamInfo ||
time_scale, arg->stream_info->stream_type() != kStreamVideo) {
encrypted, return false;
trick_play_factor, }
playback_rate, const VideoStreamInfo* video_info =
"") { static_cast<const VideoStreamInfo*>(arg->stream_info.get());
return arg->stream_index == stream_index && return video_info->trick_play_factor() == trick_play_factor &&
arg->stream_data_type == StreamDataType::kStreamInfo && video_info->playback_rate() == playback_rate;
arg->stream_info->time_scale() == time_scale &&
arg->stream_info->is_encrypted() == encrypted &&
arg->stream_info->stream_type() == kStreamVideo &&
static_cast<const VideoStreamInfo*>(arg->stream_info.get())
->trick_play_factor() == trick_play_factor &&
static_cast<const VideoStreamInfo*>(arg->stream_info.get())
->playback_rate() == playback_rate;
} }
MATCHER_P3(IsKeyFrameMediaSample, stream_index, timestamp, duration, "") { MATCHER_P2(IsTrickPlaySample, timestamp, duration, "") {
return arg->stream_index == stream_index && return arg->stream_index == kStreamIndex &&
arg->stream_data_type == StreamDataType::kMediaSample && arg->stream_data_type == StreamDataType::kMediaSample &&
arg->media_sample->dts() == timestamp && arg->media_sample->dts() == timestamp &&
arg->media_sample->duration() == duration && 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<uint32_t>& 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<StreamData> 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: protected:
std::shared_ptr<TrickPlayHandler> trick_play_handler_; void SetUpAndInitializeGraph(uint32_t factor) {
input_ = std::make_shared<FakeInputMediaHandler>();
trick_play_ = std::make_shared<TrickPlayHandler>(factor);
output_ = std::make_shared<MockOutputMediaHandler>();
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<std::shared_ptr<MediaSample>> CreateSamples(
size_t count,
uint64_t start_time,
uint64_t frame_duration,
size_t key_frame_frequency) {
std::vector<std::shared_ptr<MediaSample>> 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<FakeInputMediaHandler> input_;
std::shared_ptr<MediaHandler> trick_play_;
std::shared_ptr<MockOutputMediaHandler> output_;
}; };
// This test makes sure the audio stream is rejected by the trick play handler. // This test makes sure that audio streams are rejected by trick play handlers.
TEST_F(TrickPlayHandlerTest, AudioStream) { TEST_F(TrickPlayHandlerTest, RejectsAudio) {
const std::vector<uint32_t> trick_play_factors(std::begin(kTrickPlayFactors), const uint32_t kTrickPlayFactor = 1u;
std::end(kTrickPlayFactors)); SetUpAndInitializeGraph(kTrickPlayFactor);
SetUpTrickPlayHandler(trick_play_factors);
Status status = Process(StreamData::FromStreamInfo( Status status = input()->Dispatch(
kStreamIndex0, GetAudioStreamInfo(kTimeScale))); StreamData::FromStreamInfo(kStreamIndex, GetAudioStreamInfo(kTimescale)));
Status kExpectStatus(error::TRICK_PLAY_ERROR, "Some Messages"); EXPECT_EQ(error::TRICK_PLAY_ERROR, status.error_code());
EXPECT_TRUE(status.Matches(kExpectStatus));
} }
// This test makes sure the trick play handler can process stream data // This test makes sure that when the trick play handler is initialized using
// correctly. // a non-main-stream track that only a sub set of information gets passed
TEST_F(TrickPlayHandlerTest, VideoStreamWithTrickPlay) { // through. This checks the specific case where no media samples are sent.
const std::vector<uint32_t> trick_play_factors(std::begin(kTrickPlayFactors), TEST_F(TrickPlayHandlerTest, TrickTrackNoSamples) {
std::end(kTrickPlayFactors)); // When there are no samples, play rate could not be determined and should be
SetUpTrickPlayHandler(trick_play_factors); // set to 0.
const int64_t kPlayRate = 0;
const uint32_t kTrickPlayFactor = 1u;
ASSERT_OK(Process(StreamData::FromStreamInfo( SetUpAndInitializeGraph(kTrickPlayFactor);
kStreamIndex0, GetVideoStreamInfo(kTimeScale))));
// The stream info is cached, so the output is empty.
EXPECT_THAT(
GetOutputStreamDataVector(),
ElementsAre(IsStreamInfo(kStreamIndex0, kTimeScale, !kEncrypted)));
ClearOutputStreamDataVector();
const int kVideoStartTimestamp = 12345; {
// Group of Picture size, the frequency of key frames. testing::InSequence s;
const int kGOPSize = 3; EXPECT_CALL(*output(),
for (int i = 0; i < 3; ++i) { OnProcess(IsTrickPlayVideoStream(kTrickPlayFactor, kPlayRate)));
const bool is_key_frame = (i % kGOPSize == 0); EXPECT_CALL(*output(), OnFlush(kStreamIndex));
ASSERT_OK(Process(StreamData::FromMediaSample(
kStreamIndex0,
GetMediaSample(
kVideoStartTimestamp + kDuration * i,
kDuration,
is_key_frame))));
} }
EXPECT_THAT( ASSERT_OK(input()->Dispatch(StreamData::FromStreamInfo(
GetOutputStreamDataVector(), kStreamIndex, GetVideoStreamInfo(kTimescale))));
ElementsAre( ASSERT_OK(input()->FlushAllDownstreams());
// 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( // This test makes sure that when the trick play handler is initialized using
GetOutputStreamDataVector(), // a non-main-stream track that only a sub set of information gets passed
ElementsAre( // through.
// Frame 3, key frame. TEST_F(TrickPlayHandlerTest, TrickTrackWithSamplesOnlyGetsKeyFrames) {
IsKeyFrameMediaSample( const uint32_t kTrickPlayFactor = 1u;
kStreamIndex0, kVideoStartTimestamp + kDuration * 3, kDuration), const int64_t kStartTime = 0;
// Stream info, TrickPlayFactor = 1. const int64_t kDuration = 100;
IsTrickPlayVideoStreamInfo( const int64_t kKeyFrameRate = 3;
kStreamIndex1, kTimeScale, !kEncrypted, kTrickPlayFactors[0],
static_cast<uint32_t>(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 const int64_t kPlayRate = kKeyFrameRate * kTrickPlayFactor;
// ElementsAre supports at most 10 elements. const int64_t kTrickPlaySampleDuration = kDuration * kPlayRate;
for (int i = 6; i < 8; ++i) {
const bool is_key_frame = (i % kGOPSize == 0); SetUpAndInitializeGraph(kTrickPlayFactor);
ASSERT_OK(Process(StreamData::FromMediaSample(
kStreamIndex0, // Only some samples we send to trick play should also be sent to our mock
GetMediaSample( // handler.
kVideoStartTimestamp + kDuration * i, {
kDuration, testing::InSequence s;
is_key_frame)))); 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( std::vector<std::shared_ptr<MediaSample>> samples =
GetOutputStreamDataVector(), CreateSamples(9 /* sample count */, kStartTime, kDuration, kKeyFrameRate);
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<uint32_t>(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)); ASSERT_OK(input()->Dispatch(StreamData::FromStreamInfo(
EXPECT_THAT(GetOutputStreamDataVector(), kStreamIndex, GetVideoStreamInfo(kTimescale))));
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. for (const auto& sample : samples) {
ASSERT_OK(FlushStream(0)); ASSERT_OK(
EXPECT_THAT(GetOutputStreamDataVector(), IsEmpty()); input()->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample)));
} }
// This test makes sure the trick play handler can process stream data ASSERT_OK(input()->FlushAllDownstreams());
// correctly with a decreasing order of trick play factors.
TEST_F(TrickPlayHandlerTest, VideoStreamWithDecreasingTrickPlayFactors) {
const std::vector<uint32_t> trick_play_factors(
std::begin(kTrickPlayFactorsDecreasing),
std::end(kTrickPlayFactorsDecreasing));
SetUpTrickPlayHandler(trick_play_factors);
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 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))));
} }
EXPECT_THAT( // This test makes sure that when the trick play handler is initialized using
GetOutputStreamDataVector(), // a non-main-stream track that only a sub set of information gets passed
ElementsAre( // through.
// Frame 0, key frame. TEST_F(TrickPlayHandlerTest, TrickTrackWithSamples) {
IsMediaSample(kStreamIndex0, kVideoStartTimestamp, kDuration, const uint32_t kTrickPlayFactor = 2u;
!kEncrypted), const int64_t kStartTime = 0;
// Frame 1. const int64_t kDuration = 100;
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration, const int64_t kKeyFrameRate = 2;
kDuration, !kEncrypted),
// Frame 2.
IsMediaSample(kStreamIndex0, kVideoStartTimestamp + kDuration * 2,
kDuration, !kEncrypted)));
ClearOutputStreamDataVector();
// This expectation are separated from the expectation above because const int64_t kPlayRate = kKeyFrameRate * kTrickPlayFactor;
// ElementsAre supports at most 10 elements. const int64_t kTrickPlaySampleDuration = kDuration * kPlayRate;
for (int i = 3; i < 6; ++i) {
const bool is_key_frame = (i % kGOPSize == 0); SetUpAndInitializeGraph(kTrickPlayFactor);
ASSERT_OK(Process(StreamData::FromMediaSample(
kStreamIndex0, // Only some samples we send to trick play should also be sent to our mock
GetMediaSample( // handler.
kVideoStartTimestamp + kDuration * i, {
kDuration, testing::InSequence s;
is_key_frame)))); 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( std::vector<std::shared_ptr<MediaSample>> samples =
GetOutputStreamDataVector(), CreateSamples(8 /* sample count */, kStartTime, kDuration, kKeyFrameRate);
ElementsAre(
// Frame 3, key frame.
IsKeyFrameMediaSample(
kStreamIndex0, kVideoStartTimestamp + kDuration * 3, kDuration),
// Stream info, TrickPlayFactor = 1.
IsTrickPlayVideoStreamInfo(
kStreamIndex2, kTimeScale, !kEncrypted,
kTrickPlayFactorsDecreasing[1],
static_cast<uint32_t>(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();
// This expectation are separated from the expectation above because ASSERT_OK(input()->Dispatch(StreamData::FromStreamInfo(
// ElementsAre supports at most 10 elements. kStreamIndex, GetVideoStreamInfo(kTimescale))));
for (int i = 6; i < 8; ++i) {
const bool is_key_frame = (i % kGOPSize == 0); for (const auto& sample : samples) {
ASSERT_OK(Process(StreamData::FromMediaSample( ASSERT_OK(
kStreamIndex0, input()->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample)));
GetMediaSample(
kVideoStartTimestamp + kDuration * i,
kDuration,
is_key_frame))));
} }
EXPECT_THAT( ASSERT_OK(input()->FlushAllDownstreams());
GetOutputStreamDataVector(), }
ElementsAre(
// Frame 6, key frame.
IsKeyFrameMediaSample(
kStreamIndex0, kVideoStartTimestamp + kDuration * 6, kDuration),
// Stream info, TrickPlayFactor = 2.
IsTrickPlayVideoStreamInfo(
kStreamIndex1, kTimeScale, !kEncrypted,
kTrickPlayFactorsDecreasing[0],
static_cast<uint32_t>(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();
ASSERT_OK(FlushStream(0)); TEST_F(TrickPlayHandlerTest, TrickTrackWithSamplesAndSegments) {
EXPECT_THAT(GetOutputStreamDataVector(), const uint32_t kTrickPlayFactor = 1u;
ElementsAre( const int64_t kStartTime = 0;
// Frame 6, TrickPlayFactor = 2. const int64_t kDuration = 100;
IsKeyFrameMediaSample(kStreamIndex1, const int64_t kKeyFrameRate = 2;
kVideoStartTimestamp + kDuration * 6,
kDuration * 2),
// Frame 6, TrickPlayFactor = 1.
IsKeyFrameMediaSample(kStreamIndex2,
kVideoStartTimestamp + kDuration * 6,
kDuration * 2)));
ClearOutputStreamDataVector();
// Flush again, get nothing. const int64_t kPlayRate = kKeyFrameRate * kTrickPlayFactor;
ASSERT_OK(FlushStream(0)); const int64_t kTrickPlaySampleDuration = kDuration * kPlayRate;
EXPECT_THAT(GetOutputStreamDataVector(), IsEmpty());
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<std::shared_ptr<MediaSample>> segment_one_samples =
CreateSamples(4 /* sample count */, kStartTime, kDuration, kKeyFrameRate);
std::vector<std::shared_ptr<MediaSample>> 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 } // namespace media

View File

@ -34,6 +34,7 @@
#include "packager/media/formats/mp2t/ts_muxer.h" #include "packager/media/formats/mp2t/ts_muxer.h"
#include "packager/media/formats/mp4/mp4_muxer.h" #include "packager/media/formats/mp4/mp4_muxer.h"
#include "packager/media/formats/webm/webm_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/media/trick_play/trick_play_handler.h"
#include "packager/mpd/base/dash_iop_mpd_notifier.h" #include "packager/mpd/base/dash_iop_mpd_notifier.h"
#include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/media_info.pb.h"
@ -211,12 +212,18 @@ class StreamDescriptorCompareFn {
public: public:
bool operator()(const StreamDescriptor& a, const StreamDescriptor& b) { bool operator()(const StreamDescriptor& a, const StreamDescriptor& b) {
if (a.input == b.input) { if (a.input == b.input) {
if (a.stream_selector == b.stream_selector) if (a.stream_selector == b.stream_selector) {
// Stream with high trick_play_factor is at the beginning. // 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; return a.trick_play_factor > b.trick_play_factor;
else }
} else {
return a.stream_selector < b.stream_selector; return a.stream_selector < b.stream_selector;
} }
}
return a.input < b.input; return a.input < b.input;
} }
@ -444,44 +451,46 @@ Status CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
DCHECK(!(mpd_notifier && hls_notifier)); DCHECK(!(mpd_notifier && hls_notifier));
DCHECK(jobs); DCHECK(jobs);
// Demuxers are shared among all streams with the same input.
std::shared_ptr<Demuxer> demuxer; std::shared_ptr<Demuxer> demuxer;
std::shared_ptr<TrickPlayHandler> trick_play_handler; std::shared_ptr<MediaHandler> replicator;
std::string previous_input; std::string previous_input;
std::string previous_stream_selector; std::string previous_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);
// Process stream descriptor. // Start at -1 so that it will be at 0 on the first iteration.
if (stream_iter->stream_selector == "text" && int stream_number = -1;
output_format != CONTAINER_MOV) {
// 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; MediaInfo text_media_info;
if (!StreamInfoToTextMediaInfo(*stream_iter, &text_media_info)) { if (!StreamInfoToTextMediaInfo(stream, &text_media_info)) {
return Status(error::INVALID_ARGUMENT, return Status(error::INVALID_ARGUMENT,
"Could not create media info for stream."); "Could not create media info for stream.");
} }
if (mpd_notifier) { if (mpd_notifier) {
uint32_t unused; uint32_t unused;
if (!mpd_notifier->NotifyNewContainer(text_media_info, &unused)) { if (mpd_notifier->NotifyNewContainer(text_media_info, &unused)) {
LOG(ERROR) << "Failed to process text file " << stream_iter->input;
} else {
mpd_notifier->Flush(); mpd_notifier->Flush();
} else {
return Status(error::PARSER_FAILURE,
"Failed to process text file " + stream.input);
} }
} else if (packaging_params.output_media_info) { } else if (packaging_params.output_media_info) {
VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile( VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile(
text_media_info, stream_iter->output + kMediaInfoSuffix); text_media_info, stream.output + kMediaInfoSuffix);
} }
continue; continue;
} }
if (stream_iter->input != previous_input) { // If we changed our input files, we need a new demuxer.
// New remux job needed. Create demux and job thread. if (previous_input != stream.input) {
demuxer = std::make_shared<Demuxer>(stream_iter->input); demuxer = std::make_shared<Demuxer>(stream.input);
demuxer->set_dump_stream_info( demuxer->set_dump_stream_info(
packaging_params.test_params.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)); demuxer->SetKeySource(std::move(decryption_key_source));
} }
jobs->emplace_back(new media::Job("RemuxJob", demuxer)); 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.language.empty()) {
if (stream_iter->output.empty() && stream_iter->segment_template.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; continue;
} }
DCHECK(!jobs->empty());
// Each stream selector requires an individual trick play handler. if (new_source) {
// E.g., an input with two video streams needs two trick play handlers. replicator = std::make_shared<Replicator>();
// TODO(hmchen): add a test case in packager_test.py for two video streams
// input. std::shared_ptr<MediaHandler> chunker =
if (stream_iter->stream_selector != previous_stream_selector) { std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
previous_stream_selector = stream_iter->stream_selector;
trick_play_handler.reset(); std::shared_ptr<MediaHandler> 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. // Create the muxer (output) for this track.
std::unique_ptr<MuxerListener> muxer_listener = CreateMuxerListener( std::unique_ptr<MuxerListener> muxer_listener = CreateMuxerListener(
*stream_iter, stream_number, packaging_params.output_media_info, stream, stream_number, packaging_params.output_media_info, mpd_notifier,
mpd_notifier, hls_notifier); hls_notifier);
std::shared_ptr<Muxer> muxer = CreateMuxer( std::shared_ptr<Muxer> muxer = CreateMuxer(
packaging_params, *stream_iter, packaging_params, stream,
packaging_params.test_params.inject_fake_clock ? fake_clock : nullptr, packaging_params.test_params.inject_fake_clock ? fake_clock : nullptr,
std::move(muxer_listener)); std::move(muxer_listener));
if (!muxer) { if (!muxer) {
return Status(error::INVALID_ARGUMENT, "Failed to create muxer for " + return Status(error::INVALID_ARGUMENT, "Failed to create muxer for " +
stream_iter->input + ":" + stream.input + ":" +
stream_iter->stream_selector); stream.stream_selector);
} }
// Create a new trick_play_handler. Note that the stream_decriptors std::shared_ptr<MediaHandler> trick_play;
// are sorted so that for the same input and stream_selector, the main if (stream.trick_play_factor) {
// stream is always the last one following the trick play streams. trick_play = std::make_shared<TrickPlayHandler>(stream.trick_play_factor);
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<std::shared_ptr<MediaHandler>> handlers;
auto chunking_handler =
std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
handlers.push_back(chunking_handler);
std::shared_ptr<MediaHandler> 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));
} }
Status status; Status status;
status.Update( if (trick_play) {
demuxer->SetHandler(stream_iter->stream_selector, chunking_handler)); status.Update(replicator->AddHandler(trick_play));
status.Update(ConnectHandlers(handlers)); status.Update(trick_play->AddHandler(muxer));
} else {
status.Update(replicator->AddHandler(muxer));
}
if (!status.ok()) { if (!status.ok()) {
return status; return status;
} }
if (!stream_iter->language.empty())
demuxer->SetLanguageOverride(stream_iter->stream_selector,
stream_iter->language);
} }
// Initialize processing graph. // Initialize processing graph.

View File

@ -24,8 +24,8 @@
'dependencies': [ 'dependencies': [
'file/file.gyp:file', 'file/file.gyp:file',
'hls/hls.gyp:hls_builder', 'hls/hls.gyp:hls_builder',
'media/codecs/codecs.gyp:codecs',
'media/chunking/chunking.gyp:chunking', 'media/chunking/chunking.gyp:chunking',
'media/codecs/codecs.gyp:codecs',
'media/demuxer/demuxer.gyp:demuxer', 'media/demuxer/demuxer.gyp:demuxer',
'media/event/media_event.gyp:media_event', 'media/event/media_event.gyp:media_event',
'media/formats/mp2t/mp2t.gyp:mp2t', 'media/formats/mp2t/mp2t.gyp:mp2t',
@ -34,6 +34,7 @@
'media/formats/webm/webm.gyp:webm', 'media/formats/webm/webm.gyp:webm',
'media/formats/webvtt/webvtt.gyp:webvtt', 'media/formats/webvtt/webvtt.gyp:webvtt',
'media/formats/wvm/wvm.gyp:wvm', 'media/formats/wvm/wvm.gyp:wvm',
'media/replicator/replicator.gyp:replicator',
'media/trick_play/trick_play.gyp:trick_play', 'media/trick_play/trick_play.gyp:trick_play',
'mpd/mpd.gyp:mpd_builder', 'mpd/mpd.gyp:mpd_builder',
'third_party/boringssl/boringssl.gyp:boringssl', 'third_party/boringssl/boringssl.gyp:boringssl',