2017-02-27 17:22:25 +00:00
|
|
|
// 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/trick_play/trick_play_handler.h"
|
|
|
|
|
|
|
|
#include "packager/base/logging.h"
|
|
|
|
#include "packager/media/base/video_stream_info.h"
|
|
|
|
|
|
|
|
namespace shaka {
|
|
|
|
namespace media {
|
2017-03-21 23:14:46 +00:00
|
|
|
namespace {
|
2017-08-28 22:27:05 +00:00
|
|
|
const size_t kStreamIndexIn = 0;
|
|
|
|
const size_t kStreamIndexOut = 0;
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
TrickPlayHandler::TrickPlayHandler(uint32_t factor) : factor_(factor) {
|
|
|
|
DCHECK_GE(factor, 1u)
|
|
|
|
<< "Trick Play Handles must have a factor of 1 or higher.";
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
Status TrickPlayHandler::InitializeInternal() {
|
|
|
|
return Status::OK;
|
|
|
|
}
|
2017-03-21 23:14:46 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
Status TrickPlayHandler::Process(std::unique_ptr<StreamData> stream_data) {
|
|
|
|
DCHECK(stream_data);
|
|
|
|
DCHECK_EQ(stream_data->stream_index, kStreamIndexIn);
|
2017-02-27 17:22:25 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
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.");
|
|
|
|
}
|
2017-03-21 23:14:46 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
Status TrickPlayHandler::OnFlushRequest(size_t input_stream_index) {
|
|
|
|
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 s.ok() ? MediaHandler::FlushAllDownstreams() : s;
|
2017-03-21 23:14:46 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
Status TrickPlayHandler::OnStreamInfo(const StreamInfo& info) {
|
|
|
|
if (info.stream_type() != kStreamVideo) {
|
2017-03-21 23:14:46 +00:00
|
|
|
return Status(error::TRICK_PLAY_ERROR,
|
2017-08-28 22:27:05 +00:00
|
|
|
"Trick play does not support non-video stream");
|
2017-03-21 23:14:46 +00:00
|
|
|
}
|
2017-08-28 22:27:05 +00:00
|
|
|
|
|
|
|
// 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) {
|
2017-03-21 23:14:46 +00:00
|
|
|
return Status(error::TRICK_PLAY_ERROR,
|
2017-08-28 22:27:05 +00:00
|
|
|
"This stream is already a trick play stream.");
|
2017-03-21 23:14:46 +00:00
|
|
|
}
|
2017-08-28 22:27:05 +00:00
|
|
|
|
|
|
|
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_));
|
2017-03-21 23:14:46 +00:00
|
|
|
|
2017-02-27 17:22:25 +00:00
|
|
|
return Status::OK;
|
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
Status TrickPlayHandler::OnSegmentInfo(
|
|
|
|
std::shared_ptr<const SegmentInfo> info) {
|
|
|
|
if (delayed_messages_.empty()) {
|
|
|
|
return Status(error::TRICK_PLAY_ERROR,
|
|
|
|
"Cannot handle segments with no preceding samples.");
|
|
|
|
}
|
2017-09-12 17:24:24 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// Trick play does not care about sub segments, only full segments matter.
|
|
|
|
if (info->is_subsegment) {
|
|
|
|
return Status::OK;
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
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;
|
2017-09-12 17:24:24 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
default:
|
|
|
|
return Status(error::TRICK_PLAY_ERROR,
|
|
|
|
"Unexpected sample in trick play deferred queue : type=" +
|
|
|
|
std::to_string(static_cast<int>(previous_type)));
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
2017-08-28 22:27:05 +00:00
|
|
|
}
|
2017-02-27 17:22:25 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
Status TrickPlayHandler::OnMediaSample(const MediaSample& sample) {
|
|
|
|
total_frames_++;
|
2017-03-21 23:14:46 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
if (sample.is_key_frame()) {
|
|
|
|
total_key_frames_++;
|
2017-02-27 17:22:25 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
if ((total_key_frames_ - 1) % factor_ == 0) {
|
|
|
|
return OnTrickFrame(sample);
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
2017-08-28 22:27:05 +00:00
|
|
|
}
|
2017-02-27 17:22:25 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// 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_);
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// 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());
|
2017-03-21 23:14:46 +00:00
|
|
|
|
2017-02-27 17:22:25 +00:00
|
|
|
return Status::OK;
|
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
Status TrickPlayHandler::OnTrickFrame(const MediaSample& sample) {
|
|
|
|
total_trick_frames_++;
|
2017-02-27 17:22:25 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// Make a message we can store until later.
|
|
|
|
previous_trick_frame_ = MediaSample::CopyFrom(sample);
|
2017-03-21 23:14:46 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// 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_));
|
2017-02-27 17:22:25 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// 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;
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// 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();
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
2017-08-28 22:27:05 +00:00
|
|
|
return s;
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace media
|
|
|
|
} // namespace shaka
|