2023-12-01 17:32:19 +00:00
|
|
|
// Copyright 2017 Google LLC. All rights reserved.
|
2017-02-27 17:22:25 +00:00
|
|
|
//
|
|
|
|
// 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
|
|
|
|
|
2023-12-01 17:32:19 +00:00
|
|
|
#include <packager/media/trick_play/trick_play_handler.h>
|
2017-02-27 17:22:25 +00:00
|
|
|
|
|
|
|
#include <gmock/gmock.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
2023-12-01 17:32:19 +00:00
|
|
|
#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/status_test_util.h>
|
2017-02-27 17:22:25 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
using ::testing::_;
|
|
|
|
|
2017-02-27 17:22:25 +00:00
|
|
|
namespace shaka {
|
|
|
|
namespace media {
|
|
|
|
namespace {
|
2017-09-26 16:00:33 +00:00
|
|
|
const size_t kInputCount = 1;
|
|
|
|
const size_t kOutputCount = 1;
|
|
|
|
const size_t kInputIndex = 0;
|
|
|
|
const size_t kOutputIndex = 0;
|
2017-08-28 22:27:05 +00:00
|
|
|
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.
|
2021-08-04 18:56:44 +00:00
|
|
|
const int32_t kTimescale = 1000;
|
2017-08-28 22:27:05 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
const bool kKeyFrame = true;
|
2017-02-27 17:22:25 +00:00
|
|
|
} // namespace
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
class TrickPlayHandlerTest : public MediaHandlerTestBase {
|
|
|
|
protected:
|
|
|
|
void SetUpAndInitializeGraph(uint32_t factor) {
|
2017-09-26 16:00:33 +00:00
|
|
|
ASSERT_OK(MediaHandlerTestBase::SetUpAndInitializeGraph(
|
2018-06-14 21:01:55 +00:00
|
|
|
std::make_shared<TrickPlayHandler>(factor), kInputCount, kOutputCount));
|
|
|
|
}
|
|
|
|
|
|
|
|
Status DispatchVideoInfo() {
|
|
|
|
auto info = GetVideoStreamInfo(kTimescale);
|
|
|
|
auto data = StreamData::FromStreamInfo(kStreamIndex, std::move(info));
|
|
|
|
return Input(kInputIndex)->Dispatch(std::move(data));
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
Status DispatchSample(int64_t time, int64_t duration, bool keyframe) {
|
|
|
|
auto sample = GetMediaSample(time, duration, keyframe);
|
|
|
|
auto data = StreamData::FromMediaSample(kStreamIndex, std::move(sample));
|
|
|
|
return Input(kInputIndex)->Dispatch(std::move(data));
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
2018-06-14 21:01:55 +00:00
|
|
|
|
|
|
|
Status DispatchSegment(int64_t start_time, int64_t duration) {
|
|
|
|
const bool kSubSegment = true;
|
|
|
|
|
|
|
|
auto info = GetSegmentInfo(start_time, duration, !kSubSegment);
|
|
|
|
auto data = StreamData::FromSegmentInfo(kStreamIndex, std::move(info));
|
|
|
|
return Input(kInputIndex)->Dispatch(std::move(data));
|
|
|
|
}
|
|
|
|
|
|
|
|
Status Flush() { return Input(kInputIndex)->FlushAllDownstreams(); }
|
2017-02-27 17:22:25 +00:00
|
|
|
};
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// This test makes sure that audio streams are rejected by trick play handlers.
|
|
|
|
TEST_F(TrickPlayHandlerTest, RejectsAudio) {
|
|
|
|
const uint32_t kTrickPlayFactor = 1u;
|
|
|
|
SetUpAndInitializeGraph(kTrickPlayFactor);
|
2017-02-27 17:22:25 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
auto info = GetAudioStreamInfo(kTimescale);
|
|
|
|
auto data = StreamData::FromStreamInfo(kStreamIndex, std::move(info));
|
|
|
|
Status status = Input(kInputIndex)->Dispatch(std::move(data));
|
2017-08-28 22:27:05 +00:00
|
|
|
EXPECT_EQ(error::TRICK_PLAY_ERROR, status.error_code());
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// 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;
|
2017-09-26 16:00:33 +00:00
|
|
|
EXPECT_CALL(*Output(kOutputIndex),
|
2018-06-14 21:01:55 +00:00
|
|
|
OnProcess(IsVideoStream(_, kTrickPlayFactor, kPlayRate)));
|
|
|
|
EXPECT_CALL(*Output(kOutputIndex), OnFlush(_));
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
ASSERT_OK(DispatchVideoInfo());
|
|
|
|
ASSERT_OK(Flush());
|
2017-08-28 22:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
const int64_t kFrameDuration = 100;
|
|
|
|
const int64_t kFrame0 = 0;
|
|
|
|
const int64_t kFrame1 = 100;
|
|
|
|
const int64_t kFrame2 = 200;
|
|
|
|
const int64_t kFrame3 = 300;
|
|
|
|
const int64_t kFrame4 = 400;
|
|
|
|
const int64_t kFrame5 = 500;
|
|
|
|
const int64_t kFrame6 = 600;
|
|
|
|
const int64_t kFrame7 = 700;
|
|
|
|
const int64_t kFrame8 = 800;
|
|
|
|
|
|
|
|
// Key frame every three frames.
|
|
|
|
// Use every key frame in the trick play stream.
|
|
|
|
// A single trick play frame has 3 times the duration of a normal frame.
|
|
|
|
const int64_t kPlayRate = 3;
|
|
|
|
const int64_t kTrickPlayDuration = kFrameDuration * 3;
|
2017-08-28 22:27:05 +00:00
|
|
|
|
|
|
|
SetUpAndInitializeGraph(kTrickPlayFactor);
|
|
|
|
|
|
|
|
// Only some samples we send to trick play should also be sent to our mock
|
|
|
|
// handler.
|
|
|
|
{
|
|
|
|
testing::InSequence s;
|
2017-09-26 16:00:33 +00:00
|
|
|
EXPECT_CALL(*Output(kOutputIndex),
|
2018-06-14 21:01:55 +00:00
|
|
|
OnProcess(IsVideoStream(_, kTrickPlayFactor, kPlayRate)));
|
|
|
|
EXPECT_CALL(
|
|
|
|
*Output(kOutputIndex),
|
|
|
|
OnProcess(IsMediaSample(_, kFrame0, kTrickPlayDuration, _, kKeyFrame)));
|
|
|
|
EXPECT_CALL(
|
|
|
|
*Output(kOutputIndex),
|
|
|
|
OnProcess(IsMediaSample(_, kFrame3, kTrickPlayDuration, _, kKeyFrame)));
|
|
|
|
EXPECT_CALL(
|
|
|
|
*Output(kOutputIndex),
|
|
|
|
OnProcess(IsMediaSample(_, kFrame6, kTrickPlayDuration, _, kKeyFrame)));
|
|
|
|
EXPECT_CALL(*Output(kOutputIndex), OnFlush(_));
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
ASSERT_OK(DispatchVideoInfo());
|
2017-08-28 22:27:05 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
// GOP 1
|
|
|
|
ASSERT_OK(DispatchSample(kFrame0, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame1, kFrameDuration, !kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame2, kFrameDuration, !kKeyFrame));
|
2017-08-28 22:27:05 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
// GOP 2
|
|
|
|
ASSERT_OK(DispatchSample(kFrame3, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame4, kFrameDuration, !kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame5, kFrameDuration, !kKeyFrame));
|
2017-02-27 17:22:25 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
// GOP 3
|
|
|
|
ASSERT_OK(DispatchSample(kFrame6, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame7, kFrameDuration, !kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame8, kFrameDuration, !kKeyFrame));
|
|
|
|
|
|
|
|
ASSERT_OK(Flush());
|
2017-03-21 23:14:46 +00:00
|
|
|
}
|
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// 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;
|
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
const int64_t kFrameDuration = 100;
|
|
|
|
const int64_t kFrame0 = 0;
|
|
|
|
const int64_t kFrame1 = 100;
|
|
|
|
const int64_t kFrame2 = 200;
|
|
|
|
const int64_t kFrame3 = 300;
|
|
|
|
const int64_t kFrame4 = 400;
|
|
|
|
const int64_t kFrame5 = 500;
|
|
|
|
const int64_t kFrame6 = 600;
|
|
|
|
const int64_t kFrame7 = 700;
|
|
|
|
|
|
|
|
// Key frame every two frames.
|
|
|
|
// Use every second key frame in the trick play stream.
|
|
|
|
// A single trick play frame has 4 times the duration of a normal frame.
|
|
|
|
const int64_t kPlayRate = 4;
|
|
|
|
const int64_t kTrickPlayDuration = kFrameDuration * 4;
|
2017-08-28 22:27:05 +00:00
|
|
|
|
|
|
|
SetUpAndInitializeGraph(kTrickPlayFactor);
|
|
|
|
|
|
|
|
// Only some samples we send to trick play should also be sent to our mock
|
|
|
|
// handler.
|
|
|
|
{
|
|
|
|
testing::InSequence s;
|
2017-09-26 16:00:33 +00:00
|
|
|
EXPECT_CALL(*Output(kOutputIndex),
|
2018-06-14 21:01:55 +00:00
|
|
|
OnProcess(IsVideoStream(_, kTrickPlayFactor, kPlayRate)));
|
|
|
|
EXPECT_CALL(
|
|
|
|
*Output(kOutputIndex),
|
|
|
|
OnProcess(IsMediaSample(_, kFrame0, kTrickPlayDuration, _, kKeyFrame)));
|
|
|
|
EXPECT_CALL(
|
|
|
|
*Output(kOutputIndex),
|
|
|
|
OnProcess(IsMediaSample(_, kFrame4, kTrickPlayDuration, _, kKeyFrame)));
|
|
|
|
EXPECT_CALL(*Output(kOutputIndex), OnFlush(_));
|
2017-08-28 22:27:05 +00:00
|
|
|
}
|
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
ASSERT_OK(DispatchVideoInfo());
|
2017-08-28 22:27:05 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
// GOP 1
|
|
|
|
ASSERT_OK(DispatchSample(kFrame0, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame1, kFrameDuration, !kKeyFrame));
|
2017-08-28 22:27:05 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
// GOP 2
|
|
|
|
ASSERT_OK(DispatchSample(kFrame2, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame3, kFrameDuration, !kKeyFrame));
|
|
|
|
|
|
|
|
// GOP 3
|
|
|
|
ASSERT_OK(DispatchSample(kFrame4, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame5, kFrameDuration, !kKeyFrame));
|
|
|
|
|
|
|
|
// GOP 4
|
|
|
|
ASSERT_OK(DispatchSample(kFrame6, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame7, kFrameDuration, !kKeyFrame));
|
2017-03-21 23:14:46 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
ASSERT_OK(Flush());
|
2017-08-28 22:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TrickPlayHandlerTest, TrickTrackWithSamplesAndSegments) {
|
|
|
|
const uint32_t kTrickPlayFactor = 1u;
|
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
const int64_t kFrameDuration = 100;
|
|
|
|
const int64_t kFrame0 = 0;
|
|
|
|
const int64_t kFrame1 = 100;
|
|
|
|
const int64_t kFrame2 = 200;
|
|
|
|
const int64_t kFrame3 = 300;
|
|
|
|
const int64_t kFrame4 = 400;
|
|
|
|
const int64_t kFrame5 = 500;
|
|
|
|
const int64_t kFrame6 = 600;
|
|
|
|
const int64_t kFrame7 = 700;
|
|
|
|
|
|
|
|
const int64_t kSegmentDuration = 400;
|
|
|
|
const int64_t kSegment0 = 0;
|
|
|
|
const int64_t kSegment1 = 400;
|
|
|
|
|
|
|
|
// Key frame every two frames.
|
|
|
|
// Use every key frame in the trick play stream.
|
|
|
|
// This means that each trick play frame covers two normal frames.
|
|
|
|
const int64_t kPlayRate = 2;
|
|
|
|
const int64_t kTrickPlayDuration = kFrameDuration * 2;
|
2017-08-28 22:27:05 +00:00
|
|
|
|
|
|
|
SetUpAndInitializeGraph(kTrickPlayFactor);
|
|
|
|
|
|
|
|
// Only some samples we send to trick play should also be sent to our mock
|
|
|
|
// handler.
|
|
|
|
{
|
|
|
|
testing::InSequence s;
|
2017-09-26 16:00:33 +00:00
|
|
|
EXPECT_CALL(*Output(kOutputIndex),
|
2018-06-14 21:01:55 +00:00
|
|
|
OnProcess(IsVideoStream(_, kTrickPlayFactor, kPlayRate)));
|
2017-08-28 22:27:05 +00:00
|
|
|
|
|
|
|
// Segment One
|
2018-06-14 21:01:55 +00:00
|
|
|
EXPECT_CALL(
|
|
|
|
*Output(kOutputIndex),
|
|
|
|
OnProcess(IsMediaSample(_, kFrame0, kTrickPlayDuration, _, kKeyFrame)));
|
|
|
|
EXPECT_CALL(
|
|
|
|
*Output(kOutputIndex),
|
|
|
|
OnProcess(IsMediaSample(_, kFrame2, kTrickPlayDuration, _, kKeyFrame)));
|
2017-09-26 16:00:33 +00:00
|
|
|
EXPECT_CALL(*Output(kOutputIndex),
|
2018-06-14 21:01:55 +00:00
|
|
|
OnProcess(IsSegmentInfo(_, kSegment0, kSegmentDuration, _, _)));
|
2017-08-28 22:27:05 +00:00
|
|
|
|
|
|
|
// Segment Two
|
2017-09-26 16:00:33 +00:00
|
|
|
EXPECT_CALL(
|
|
|
|
*Output(kOutputIndex),
|
2018-06-14 21:01:55 +00:00
|
|
|
OnProcess(IsMediaSample(_, kFrame4, kTrickPlayDuration, _, kKeyFrame)));
|
|
|
|
EXPECT_CALL(
|
|
|
|
*Output(kOutputIndex),
|
|
|
|
OnProcess(IsMediaSample(_, kFrame6, kTrickPlayDuration, _, kKeyFrame)));
|
|
|
|
EXPECT_CALL(*Output(kOutputIndex),
|
|
|
|
OnProcess(IsSegmentInfo(_, kSegment1, kSegmentDuration, _, _)));
|
2017-08-28 22:27:05 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
EXPECT_CALL(*Output(kOutputIndex), OnFlush(_));
|
2017-08-28 22:27:05 +00:00
|
|
|
}
|
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
ASSERT_OK(DispatchVideoInfo());
|
2017-08-28 22:27:05 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
// GOP 1
|
|
|
|
ASSERT_OK(DispatchSample(kFrame0, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame1, kFrameDuration, !kKeyFrame));
|
2017-08-28 22:27:05 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
// GOP 2
|
|
|
|
ASSERT_OK(DispatchSample(kFrame2, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame3, kFrameDuration, !kKeyFrame));
|
2017-08-28 22:27:05 +00:00
|
|
|
|
|
|
|
// Segment One
|
2018-06-14 21:01:55 +00:00
|
|
|
ASSERT_OK(DispatchSegment(kSegment0, kSegmentDuration));
|
|
|
|
|
|
|
|
// GOP 3
|
|
|
|
ASSERT_OK(DispatchSample(kFrame4, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame5, kFrameDuration, !kKeyFrame));
|
|
|
|
|
|
|
|
// GOP 4
|
|
|
|
ASSERT_OK(DispatchSample(kFrame6, kFrameDuration, kKeyFrame));
|
|
|
|
ASSERT_OK(DispatchSample(kFrame7, kFrameDuration, !kKeyFrame));
|
2017-03-21 23:14:46 +00:00
|
|
|
|
2017-08-28 22:27:05 +00:00
|
|
|
// Segment Two
|
2018-06-14 21:01:55 +00:00
|
|
|
ASSERT_OK(DispatchSegment(kSegment1, kSegmentDuration));
|
2017-03-21 23:14:46 +00:00
|
|
|
|
2018-06-14 21:01:55 +00:00
|
|
|
ASSERT_OK(Flush());
|
2017-02-27 17:22:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace media
|
|
|
|
} // namespace shaka
|