Implement trick play handler.
Change-Id: I3d288dc7c4b77fd3aca1bad2ce96ad733384bcc8
This commit is contained in:
parent
0a3ef49c11
commit
cc04698460
|
@ -30,6 +30,7 @@ enum FieldType {
|
|||
kHlsNameField,
|
||||
kHlsGroupIdField,
|
||||
kHlsPlaylistNameField,
|
||||
kTrickPlayRateField,
|
||||
};
|
||||
|
||||
struct FieldNameToTypeMapping {
|
||||
|
@ -57,6 +58,7 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
|
|||
{"hls_name", kHlsNameField},
|
||||
{"hls_group_id", kHlsGroupIdField},
|
||||
{"playlist_name", kHlsPlaylistNameField},
|
||||
{"trick_play_rate", kTrickPlayRateField},
|
||||
};
|
||||
|
||||
FieldType GetFieldType(const std::string& field_name) {
|
||||
|
|
|
@ -33,6 +33,7 @@ struct StreamDescriptor {
|
|||
std::string hls_name;
|
||||
std::string hls_group_id;
|
||||
std::string hls_playlist_name;
|
||||
int16_t trick_play_rate;
|
||||
};
|
||||
|
||||
class StreamDescriptorCompareFn {
|
||||
|
|
|
@ -27,7 +27,7 @@ const uint16_t kWidth = 10u;
|
|||
const uint16_t kHeight = 20u;
|
||||
const uint32_t kPixelWidth = 2u;
|
||||
const uint32_t kPixelHeight = 3u;
|
||||
const int16_t kTrickPlayRate = 4;
|
||||
const int16_t kTrickPlayRate = 0;
|
||||
const uint8_t kNaluLengthSize = 1u;
|
||||
const bool kEncrypted = true;
|
||||
|
||||
|
|
|
@ -63,6 +63,32 @@ std::shared_ptr<MediaSample> MediaSample::CopyFrom(const uint8_t* data,
|
|||
side_data_size, is_key_frame);
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<MediaSample> MediaSample::CopyFrom(
|
||||
const MediaSample& media_sample) {
|
||||
std::shared_ptr<MediaSample> new_media_sample = CopyFrom(
|
||||
media_sample.data(), media_sample.data_size(), media_sample.side_data(),
|
||||
media_sample.side_data_size(), media_sample.is_key_frame());
|
||||
|
||||
new_media_sample->set_dts(media_sample.dts());
|
||||
new_media_sample->set_pts(media_sample.pts());
|
||||
new_media_sample->set_is_encrypted(media_sample.is_encrypted());
|
||||
new_media_sample->set_config_id(media_sample.config_id());
|
||||
new_media_sample->set_duration(media_sample.duration());
|
||||
|
||||
if (media_sample.decrypt_config()) {
|
||||
std::unique_ptr<DecryptConfig> decrypt_config(
|
||||
new DecryptConfig(media_sample.decrypt_config()->key_id(),
|
||||
media_sample.decrypt_config()->iv(),
|
||||
media_sample.decrypt_config()->subsamples(),
|
||||
media_sample.decrypt_config()->protection_scheme(),
|
||||
media_sample.decrypt_config()->crypt_byte_block(),
|
||||
media_sample.decrypt_config()->skip_byte_block()));
|
||||
new_media_sample->set_decrypt_config(std::move(decrypt_config));
|
||||
}
|
||||
return new_media_sample;
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<MediaSample> MediaSample::FromMetadata(const uint8_t* metadata,
|
||||
size_t metadata_size) {
|
||||
|
|
|
@ -45,6 +45,10 @@ class MediaSample {
|
|||
size_t side_data_size,
|
||||
bool is_key_frame);
|
||||
|
||||
/// Make a copy of MediaSample.
|
||||
/// @param media_sample is the source MediaSample to copy from.
|
||||
static std::shared_ptr<MediaSample> CopyFrom(const MediaSample& media_sample);
|
||||
|
||||
/// Create a MediaSample object from metadata.
|
||||
/// Unlike other factory methods, this cannot be a key frame. It must be only
|
||||
/// for metadata.
|
||||
|
@ -108,12 +112,12 @@ class MediaSample {
|
|||
}
|
||||
const uint8_t* data() const {
|
||||
DCHECK(!end_of_stream());
|
||||
return &data_[0];
|
||||
return data_.data();
|
||||
}
|
||||
|
||||
uint8_t* writable_data() {
|
||||
DCHECK(!end_of_stream());
|
||||
return &data_[0];
|
||||
return data_.data();
|
||||
}
|
||||
|
||||
size_t data_size() const {
|
||||
|
@ -122,7 +126,7 @@ class MediaSample {
|
|||
}
|
||||
|
||||
const uint8_t* side_data() const {
|
||||
return &side_data_[0];
|
||||
return side_data_.data();
|
||||
}
|
||||
|
||||
size_t side_data_size() const {
|
||||
|
|
|
@ -78,6 +78,9 @@ enum Code {
|
|||
// The entity that a client attempted to create (e.g., file or directory)
|
||||
// already exists.
|
||||
ALREADY_EXISTS,
|
||||
|
||||
// Error when trying to generate trick play stream.
|
||||
TRICK_PLAY_ERROR,
|
||||
};
|
||||
|
||||
} // namespace error
|
||||
|
|
|
@ -56,6 +56,8 @@ enum Codec {
|
|||
/// Abstract class holds stream information.
|
||||
class StreamInfo {
|
||||
public:
|
||||
StreamInfo() = default;
|
||||
|
||||
StreamInfo(StreamType stream_type, int track_id, uint32_t time_scale,
|
||||
uint64_t duration, Codec codec, const std::string& codec_string,
|
||||
const uint8_t* codec_config, size_t codec_config_size,
|
||||
|
|
|
@ -22,6 +22,8 @@ enum class H26xStreamFormat {
|
|||
/// Holds video stream information.
|
||||
class VideoStreamInfo : public StreamInfo {
|
||||
public:
|
||||
VideoStreamInfo() = default;
|
||||
|
||||
/// Construct an initialized video stream info object.
|
||||
/// @param pixel_width is the width of the pixel. 0 if unknown.
|
||||
/// @param pixel_height is the height of the pixels. 0 if unknown.
|
||||
|
@ -67,6 +69,9 @@ class VideoStreamInfo : public StreamInfo {
|
|||
void set_height(uint32_t height) { height_ = height; }
|
||||
void set_pixel_width(uint32_t pixel_width) { pixel_width_ = pixel_width; }
|
||||
void set_pixel_height(uint32_t pixel_height) { pixel_height_ = pixel_height; }
|
||||
void set_trick_play_rate(int16_t trick_play_rate) {
|
||||
trick_play_rate_ = trick_play_rate;
|
||||
}
|
||||
void set_eme_init_data(const uint8_t* eme_init_data,
|
||||
size_t eme_init_data_size) {
|
||||
eme_init_data_.assign(eme_init_data, eme_init_data + eme_init_data_size);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# 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': 'trick_play',
|
||||
'type': '<(component)',
|
||||
'sources': [
|
||||
'trick_play_handler.cc',
|
||||
'trick_play_handler.h',
|
||||
],
|
||||
'dependencies': [
|
||||
'../base/media_base.gyp:media_base',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'trick_play_unittest',
|
||||
'type': '<(gtest_target_type)',
|
||||
'sources': [
|
||||
'trick_play_handler_unittest.cc',
|
||||
],
|
||||
'dependencies': [
|
||||
'../../testing/gtest.gyp:gtest',
|
||||
'../../testing/gmock.gyp:gmock',
|
||||
'../base/media_base.gyp:media_handler_test_base',
|
||||
'../test/media_test.gyp:media_test_support',
|
||||
'trick_play',
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
// 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 {
|
||||
|
||||
TrickPlayHandler::TrickPlayHandler(const TrickPlayOptions& trick_play_option)
|
||||
: trick_play_options_(trick_play_option),
|
||||
cached_stream_data_(trick_play_option.trick_play_rates.size()) {
|
||||
for (auto rate : trick_play_option.trick_play_rates) {
|
||||
CHECK_GT(rate, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TrickPlayHandler::~TrickPlayHandler() {}
|
||||
|
||||
Status TrickPlayHandler::InitializeInternal() {
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status TrickPlayHandler::Process(
|
||||
std::unique_ptr<StreamData> input_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_EQ(input_stream_data->stream_index, 0u);
|
||||
std::unique_ptr<StreamData> output_stream_data(new StreamData());
|
||||
*output_stream_data = *input_stream_data;
|
||||
Status status = Dispatch(std::move(output_stream_data));
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::shared_ptr<StreamData> stream_data(std::move(input_stream_data));
|
||||
if (stream_data->stream_data_type == StreamDataType::kStreamInfo) {
|
||||
if (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<const VideoStreamInfo&>(*stream_data->stream_info);
|
||||
if (video_stream_info.trick_play_rate() > 0) {
|
||||
status.SetError(error::TRICK_PLAY_ERROR,
|
||||
"This stream is alreay a trick play stream.");
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
if (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(stream_data);
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
if (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_options_.trick_play_rates.size(),
|
||||
cached_stream_data_.size());
|
||||
for (size_t i = 0; i < cached_stream_data_.size(); ++i) {
|
||||
int16_t rate = trick_play_options_.trick_play_rates[i];
|
||||
if (total_key_frames_ % rate == 0) {
|
||||
if (!cached_stream_data_[i].empty()) {
|
||||
Status status =
|
||||
ProcessCachedStreamData(i + 1, &cached_stream_data_[i]);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
cached_stream_data_[i].push_back(stream_data);
|
||||
}
|
||||
}
|
||||
|
||||
total_key_frames_++;
|
||||
}
|
||||
|
||||
prev_sample_end_timestamp_ =
|
||||
stream_data->media_sample->dts() + 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_options_.trick_play_rates.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) {
|
||||
ProcessCachedStreamData(i + 1, &cached_stream_data_[i]);
|
||||
}
|
||||
return MediaHandler::FlushDownstream(input_stream_index);
|
||||
}
|
||||
|
||||
Status TrickPlayHandler::ProcessCachedStreamData(
|
||||
int output_stream_index,
|
||||
std::deque<std::shared_ptr<StreamData>>* 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();
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status TrickPlayHandler::ProcessOneStreamData(
|
||||
int output_stream_index,
|
||||
const std::shared_ptr<StreamData>& stream_data) {
|
||||
uint32_t trick_play_rate =
|
||||
trick_play_options_.trick_play_rates[output_stream_index - 1];
|
||||
Status status;
|
||||
switch (stream_data->stream_data_type) {
|
||||
// trick_play_rate 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_rate(trick_play_rate);
|
||||
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<MediaSample> 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<StreamData> new_stream_data(new StreamData(*stream_data));
|
||||
new_stream_data->stream_index = output_stream_index;
|
||||
status = Dispatch(std::move(new_stream_data));
|
||||
break;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace shaka
|
|
@ -0,0 +1,94 @@
|
|||
// 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_BASE_TRICK_PLAY_HANDLER_H_
|
||||
#define PACKAGER_MEDIA_BASE_TRICK_PLAY_HANDLER_H_
|
||||
|
||||
#include "packager/media/base/media_handler.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
|
||||
struct TrickPlayOptions {
|
||||
/// Trick play rates. Note that there can be multiple trick play rates,
|
||||
/// 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<int16_t> trick_play_rates;
|
||||
};
|
||||
|
||||
/// 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_rate 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 TrickPlayHandler : public MediaHandler {
|
||||
public:
|
||||
explicit TrickPlayHandler(const TrickPlayOptions& trick_play_options);
|
||||
~TrickPlayHandler() override;
|
||||
|
||||
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:
|
||||
friend class TrickPlayHandlerTest;
|
||||
|
||||
// Process the cached stream data for one trick play stream.
|
||||
// The cached data is dispatched to the |output_stream_index|.
|
||||
// The |current_dts| is for updating the duration of key frames,
|
||||
// so that there is no gap between consecutive key frames. When
|
||||
// |current_dts| = -1, the original duration of the key frame is used.
|
||||
Status ProcessCachedStreamData(
|
||||
int 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(int output_stream_index,
|
||||
const std::shared_ptr<StreamData>& stream_data);
|
||||
|
||||
const TrickPlayOptions trick_play_options_;
|
||||
|
||||
TrickPlayHandler(const TrickPlayHandler&) = delete;
|
||||
TrickPlayHandler& operator=(const TrickPlayHandler&) = delete;
|
||||
|
||||
/// Num of key frames received.
|
||||
uint32_t total_key_frames_ = 0;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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<std::deque<std::shared_ptr<StreamData>>> cached_stream_data_;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
} // namespace shaka
|
||||
|
||||
#endif // PACKAGER_MEDIA_BASE_TRICK_PLAY_HANDLER_H_
|
|
@ -0,0 +1,211 @@
|
|||
// 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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "packager/media/base/media_handler_test_base.h"
|
||||
#include "packager/media/base/test/status_test_util.h"
|
||||
#include "packager/media/base/video_stream_info.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 size_t kStreamIndex3 = 3;
|
||||
const uint32_t kTimeScale = 800;
|
||||
const uint32_t kDuration = 200;
|
||||
const int16_t kTrickPlayRates[]{1, 2, 4};
|
||||
const bool kEncrypted = true;
|
||||
} // namespace
|
||||
|
||||
MATCHER_P4(IsTrickPlayVideoStreamInfo,
|
||||
stream_index,
|
||||
time_scale,
|
||||
encrypted,
|
||||
trick_play_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<const VideoStreamInfo*>(arg->stream_info.get())
|
||||
->trick_play_rate() == trick_play_rate;
|
||||
}
|
||||
|
||||
MATCHER_P3(IsKeyFrameMediaSample, stream_index, timestamp, duration, "") {
|
||||
return arg->stream_index == stream_index &&
|
||||
arg->stream_data_type == StreamDataType::kMediaSample &&
|
||||
arg->media_sample->dts() == timestamp &&
|
||||
arg->media_sample->duration() == duration &&
|
||||
arg->media_sample->is_key_frame() == true;
|
||||
}
|
||||
|
||||
class TrickPlayHandlerTest : public MediaHandlerTestBase {
|
||||
public:
|
||||
void SetUpTrickPlayHandler(const TrickPlayOptions& trick_play_options) {
|
||||
trick_play_handler_.reset(new TrickPlayHandler(trick_play_options));
|
||||
// The output stream size is number of trick play stream + one
|
||||
// non-trick-play stream.
|
||||
SetUpGraph(1, trick_play_options.trick_play_rates.size() + 1,
|
||||
trick_play_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);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<TrickPlayHandler> trick_play_handler_;
|
||||
};
|
||||
|
||||
// This test makes sure the audio stream is rejected by the trick play handler.
|
||||
TEST_F(TrickPlayHandlerTest, AudioStream) {
|
||||
TrickPlayOptions trick_play_options;
|
||||
trick_play_options.trick_play_rates.assign(std::begin(kTrickPlayRates),
|
||||
std::end(kTrickPlayRates));
|
||||
SetUpTrickPlayHandler(trick_play_options);
|
||||
|
||||
Status status =
|
||||
Process(GetAudioStreamInfoStreamData(kStreamIndex0, kTimeScale));
|
||||
Status kExpectStatus(error::TRICK_PLAY_ERROR, "Some Messages");
|
||||
EXPECT_TRUE(status.Matches(kExpectStatus));
|
||||
}
|
||||
|
||||
// This test makes sure the trick play handler can process stream data
|
||||
// correctly.
|
||||
TEST_F(TrickPlayHandlerTest, VideoStream) {
|
||||
TrickPlayOptions trick_play_options;
|
||||
trick_play_options.trick_play_rates.assign(std::begin(kTrickPlayRates),
|
||||
std::end(kTrickPlayRates));
|
||||
SetUpTrickPlayHandler(trick_play_options);
|
||||
|
||||
ASSERT_OK(Process(GetVideoStreamInfoStreamData(kStreamIndex0, 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(GetMediaSampleStreamData(
|
||||
kStreamIndex0, kVideoStartTimestamp + kDuration * i, kDuration,
|
||||
is_key_frame)));
|
||||
}
|
||||
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(
|
||||
// Frame 0, key frame.
|
||||
IsMediaSample(kStreamIndex0, kVideoStartTimestamp, kDuration,
|
||||
!kEncrypted),
|
||||
IsTrickPlayVideoStreamInfo(kStreamIndex1, kTimeScale, !kEncrypted,
|
||||
kTrickPlayRates[0]),
|
||||
IsTrickPlayVideoStreamInfo(kStreamIndex2, kTimeScale, !kEncrypted,
|
||||
kTrickPlayRates[1]),
|
||||
IsTrickPlayVideoStreamInfo(kStreamIndex3, kTimeScale, !kEncrypted,
|
||||
kTrickPlayRates[2]),
|
||||
// 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(GetMediaSampleStreamData(
|
||||
kStreamIndex0, kVideoStartTimestamp + kDuration * i, kDuration,
|
||||
is_key_frame)));
|
||||
}
|
||||
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(
|
||||
// Frame 3, key frame.
|
||||
IsKeyFrameMediaSample(
|
||||
kStreamIndex0, kVideoStartTimestamp + kDuration * 3, kDuration),
|
||||
// Frame 0, TrickPlayRate = 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(GetMediaSampleStreamData(
|
||||
kStreamIndex0, kVideoStartTimestamp + kDuration * i, kDuration,
|
||||
is_key_frame)));
|
||||
}
|
||||
|
||||
EXPECT_THAT(
|
||||
GetOutputStreamDataVector(),
|
||||
ElementsAre(
|
||||
// Frame 6, key frame.
|
||||
IsKeyFrameMediaSample(
|
||||
kStreamIndex0, kVideoStartTimestamp + kDuration * 6, kDuration),
|
||||
// Frame 3, TrickPlayRate = 1.
|
||||
IsKeyFrameMediaSample(kStreamIndex1,
|
||||
kVideoStartTimestamp + kDuration * 3,
|
||||
kDuration * 3),
|
||||
// Frame 0, TrickPlayRate = 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, TrickPlayRate = 1.
|
||||
IsKeyFrameMediaSample(kStreamIndex1,
|
||||
kVideoStartTimestamp + kDuration * 6,
|
||||
kDuration * 2),
|
||||
// Frame 6, TrickPlayRate = 2.
|
||||
IsKeyFrameMediaSample(kStreamIndex2,
|
||||
kVideoStartTimestamp + kDuration * 6,
|
||||
kDuration * 2),
|
||||
// Frame 0, TrickPlayRate = 4.
|
||||
IsKeyFrameMediaSample(kStreamIndex3, kVideoStartTimestamp,
|
||||
kDuration * 8)));
|
||||
ClearOutputStreamDataVector();
|
||||
|
||||
// Flush again, get nothing.
|
||||
ASSERT_OK(FlushStream(0));
|
||||
EXPECT_THAT(GetOutputStreamDataVector(), IsEmpty());
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace shaka
|
|
@ -127,6 +127,7 @@
|
|||
'media/formats/webm/webm.gyp:webm_unittest',
|
||||
'media/formats/webvtt/webvtt.gyp:webvtt_unittest',
|
||||
'media/formats/wvm/wvm.gyp:wvm_unittest',
|
||||
'media/trick_play/trick_play.gyp:trick_play_unittest',
|
||||
'mpd/mpd.gyp:mpd_unittest',
|
||||
'packager_test',
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue