shaka-packager/packager/media/trick_play/trick_play_handler_unittest.cc

291 lines
10 KiB
C++

// 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/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"
namespace shaka {
namespace media {
namespace {
const size_t kInputCount = 1;
const size_t kOutputCount = 1;
const size_t kInputIndex = 0;
const size_t kOutputIndex = 0;
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_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<const VideoStreamInfo*>(arg->stream_info.get());
return video_info->trick_play_factor() == trick_play_factor &&
video_info->playback_rate() == playback_rate;
}
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();
}
class TrickPlayHandlerTest : public MediaHandlerTestBase {
protected:
void SetUpAndInitializeGraph(uint32_t factor) {
ASSERT_OK(MediaHandlerTestBase::SetUpAndInitializeGraph(
std::make_shared<TrickPlayHandler>(factor),
kInputCount,
kOutputCount));
}
// 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;
}
};
// 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 = Input(kInputIndex)
->Dispatch(StreamData::FromStreamInfo(
kStreamIndex, GetAudioStreamInfo(kTimescale)));
EXPECT_EQ(error::TRICK_PLAY_ERROR, status.error_code());
}
// 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;
SetUpAndInitializeGraph(kTrickPlayFactor);
{
testing::InSequence s;
EXPECT_CALL(*Output(kOutputIndex),
OnProcess(IsTrickPlayVideoStream(kTrickPlayFactor, kPlayRate)));
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
}
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromStreamInfo(
kStreamIndex, GetVideoStreamInfo(kTimescale))));
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
}
// 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;
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(kOutputIndex),
OnProcess(IsTrickPlayVideoStream(kTrickPlayFactor, kPlayRate)));
for (int i = 0; i < 3; i++) {
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTrickPlaySample(kStartTime + i * kTrickPlaySampleDuration,
kTrickPlaySampleDuration)));
}
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
}
std::vector<std::shared_ptr<MediaSample>> samples =
CreateSamples(9 /* sample count */, kStartTime, kDuration, kKeyFrameRate);
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromStreamInfo(
kStreamIndex, GetVideoStreamInfo(kTimescale))));
for (const auto& sample : samples) {
ASSERT_OK(
Input(kInputIndex)
->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample)));
}
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
}
// 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(kOutputIndex),
OnProcess(IsTrickPlayVideoStream(kTrickPlayFactor, kPlayRate)));
for (int i = 0; i < 2; i++) {
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTrickPlaySample(kStartTime + i * kTrickPlaySampleDuration,
kTrickPlaySampleDuration)));
}
EXPECT_CALL(*Output(kOutputIndex), OnFlush(0u));
}
std::vector<std::shared_ptr<MediaSample>> samples =
CreateSamples(8 /* sample count */, kStartTime, kDuration, kKeyFrameRate);
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromStreamInfo(
kStreamIndex, GetVideoStreamInfo(kTimescale))));
for (const auto& sample : samples) {
ASSERT_OK(
Input(kInputIndex)
->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample)));
}
ASSERT_OK(Input(kInputIndex)->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(kOutputIndex),
OnProcess(IsTrickPlayVideoStream(kTrickPlayFactor, kPlayRate)));
// Segment One
for (int i = 0; i < 2; i++) {
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTrickPlaySample(kStartTime + kTrickPlaySampleDuration * i,
kTrickPlaySampleDuration)));
}
EXPECT_CALL(*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kStartTime, 4 * kDuration,
!kSubSegment, !kEncrypted)));
// Segment Two
for (int i = 2; i < 4; i++) {
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsTrickPlaySample(kStartTime + kTrickPlaySampleDuration * i,
kTrickPlaySampleDuration)));
}
EXPECT_CALL(
*Output(kOutputIndex),
OnProcess(IsSegmentInfo(kStreamIndex, kStartTime + 4 * kDuration,
4 * kDuration, !kSubSegment, !kEncrypted)));
EXPECT_CALL(*Output(kOutputIndex), 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(kInputIndex)
->Dispatch(StreamData::FromStreamInfo(
kStreamIndex, GetVideoStreamInfo(kTimescale))));
// Segment One
for (const auto& sample : segment_one_samples) {
ASSERT_OK(
Input(kInputIndex)
->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample)));
}
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromSegmentInfo(
kStreamIndex,
GetSegmentInfo(kStartTime, kDuration * 4, !kEncrypted))));
// Segment Two
for (const auto& sample : segment_two_samples) {
ASSERT_OK(
Input(kInputIndex)
->Dispatch(StreamData::FromMediaSample(kStreamIndex, sample)));
}
ASSERT_OK(Input(kInputIndex)
->Dispatch(StreamData::FromSegmentInfo(
kStreamIndex, GetSegmentInfo(kStartTime + 4 * kDuration,
4 * kDuration, !kEncrypted))));
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
}
} // namespace media
} // namespace shaka