Add support for 4K and 8K content.

Adds UHD1 and UHD2 track types to support 4K and 8K content.

Bug: Closes #163
Change-Id: I8fd893725cae88e9944244a48607cbaab591b401
This commit is contained in:
Kyle Alexander 2016-11-11 15:17:17 -08:00
parent d8d94da250
commit 0c2ee4c844
23 changed files with 225 additions and 47 deletions

View File

@ -334,6 +334,8 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
if (key_source) { if (key_source) {
muxer->SetKeySource(key_source, muxer->SetKeySource(key_source,
FLAGS_max_sd_pixels, FLAGS_max_sd_pixels,
FLAGS_max_hd_pixels,
FLAGS_max_uhd1_pixels,
FLAGS_clear_lead, FLAGS_clear_lead,
FLAGS_crypto_period_duration, FLAGS_crypto_period_duration,
GetProtectionScheme(FLAGS_protection_scheme)); GetProtectionScheme(FLAGS_protection_scheme));

View File

@ -37,8 +37,18 @@ DEFINE_string(policy,
"rights."); "rights.");
DEFINE_int32(max_sd_pixels, DEFINE_int32(max_sd_pixels,
768 * 576, 768 * 576,
"If the video track has more pixels per frame than max_sd_pixels, " "The video track is considered SD if its max pixels per frame is "
"it is considered as HD, SD otherwise. Default: 768 * 576."); "no higher than max_sd_pixels. Default: 442368 (768 x 576).");
DEFINE_int32(max_hd_pixels,
1920 * 1080,
"The video track is considered HD if its max pixels per frame is "
"higher than max_sd_pixels, but no higher than max_hd_pixels. "
"Default: 2073600 (1920 x 1080).");
DEFINE_int32(max_uhd1_pixels,
4096 * 2160,
"The video track is considered UHD1 if its max pixels per frame "
"is higher than max_hd_pixels, but no higher than max_uhd1_pixels."
" Otherwise it is UHD2. Default: 8847360 (4096 x 2160).");
DEFINE_string(signer, "", "The name of the signer."); DEFINE_string(signer, "", "The name of the signer.");
DEFINE_string(aes_signing_key, DEFINE_string(aes_signing_key,
"", "",
@ -119,6 +129,22 @@ bool ValidateWidevineCryptoFlags() {
PrintError("--max_sd_pixels must be positive."); PrintError("--max_sd_pixels must be positive.");
success = false; success = false;
} }
if (FLAGS_max_hd_pixels <= 0) {
PrintError("--max_hd_pixels must be positive.");
success = false;
}
if (FLAGS_max_uhd1_pixels <= 0) {
PrintError("--max_uhd1_pixels must be positive.");
success = false;
}
if (FLAGS_max_hd_pixels <= FLAGS_max_sd_pixels) {
PrintError("--max_hd_pixels must be greater than --max_sd_pixels.");
success = false;
}
if (FLAGS_max_uhd1_pixels <= FLAGS_max_hd_pixels) {
PrintError("--max_uhd1_pixels must be greater than --max_hd_pixels.");
success = false;
}
const bool aes = !FLAGS_signer.empty() && FLAGS_rsa_signing_key_path.empty(); const bool aes = !FLAGS_signer.empty() && FLAGS_rsa_signing_key_path.empty();
const char aes_label[] = const char aes_label[] =

View File

@ -18,6 +18,8 @@ DECLARE_string(key_server_url);
DECLARE_string(content_id); DECLARE_string(content_id);
DECLARE_string(policy); DECLARE_string(policy);
DECLARE_int32(max_sd_pixels); DECLARE_int32(max_sd_pixels);
DECLARE_int32(max_hd_pixels);
DECLARE_int32(max_uhd1_pixels);
DECLARE_string(signer); DECLARE_string(signer);
DECLARE_string(aes_signing_key); DECLARE_string(aes_signing_key);
DECLARE_string(aes_signing_iv); DECLARE_string(aes_signing_iv);

View File

@ -22,6 +22,10 @@ KeySource::TrackType KeySource::GetTrackTypeFromString(
return TRACK_TYPE_SD; return TRACK_TYPE_SD;
if (track_type_string == "HD") if (track_type_string == "HD")
return TRACK_TYPE_HD; return TRACK_TYPE_HD;
if (track_type_string == "UHD1")
return TRACK_TYPE_UHD1;
if (track_type_string == "UHD2")
return TRACK_TYPE_UHD2;
if (track_type_string == "AUDIO") if (track_type_string == "AUDIO")
return TRACK_TYPE_AUDIO; return TRACK_TYPE_AUDIO;
if (track_type_string == "UNSPECIFIED") if (track_type_string == "UNSPECIFIED")
@ -36,6 +40,10 @@ std::string KeySource::TrackTypeToString(TrackType track_type) {
return "SD"; return "SD";
case TRACK_TYPE_HD: case TRACK_TYPE_HD:
return "HD"; return "HD";
case TRACK_TYPE_UHD1:
return "UHD1";
case TRACK_TYPE_UHD2:
return "UHD2";
case TRACK_TYPE_AUDIO: case TRACK_TYPE_AUDIO:
return "AUDIO"; return "AUDIO";
default: default:

View File

@ -33,9 +33,11 @@ class KeySource {
TRACK_TYPE_UNKNOWN = 0, TRACK_TYPE_UNKNOWN = 0,
TRACK_TYPE_SD = 1, TRACK_TYPE_SD = 1,
TRACK_TYPE_HD = 2, TRACK_TYPE_HD = 2,
TRACK_TYPE_AUDIO = 3, TRACK_TYPE_UHD1 = 3,
TRACK_TYPE_UNSPECIFIED = 4, TRACK_TYPE_UHD2 = 4,
NUM_VALID_TRACK_TYPES = 4 TRACK_TYPE_AUDIO = 5,
TRACK_TYPE_UNSPECIFIED = 6,
NUM_VALID_TRACK_TYPES = 6
}; };
KeySource(); KeySource();

View File

@ -20,6 +20,8 @@ Muxer::Muxer(const MuxerOptions& options)
initialized_(false), initialized_(false),
encryption_key_source_(NULL), encryption_key_source_(NULL),
max_sd_pixels_(0), max_sd_pixels_(0),
max_hd_pixels_(0),
max_uhd1_pixels_(0),
clear_lead_in_seconds_(0), clear_lead_in_seconds_(0),
crypto_period_duration_in_seconds_(0), crypto_period_duration_in_seconds_(0),
protection_scheme_(FOURCC_NULL), protection_scheme_(FOURCC_NULL),
@ -30,12 +32,16 @@ Muxer::~Muxer() {}
void Muxer::SetKeySource(KeySource* encryption_key_source, void Muxer::SetKeySource(KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds, double clear_lead_in_seconds,
double crypto_period_duration_in_seconds, double crypto_period_duration_in_seconds,
FourCC protection_scheme) { FourCC protection_scheme) {
DCHECK(encryption_key_source); DCHECK(encryption_key_source);
encryption_key_source_ = encryption_key_source; encryption_key_source_ = encryption_key_source;
max_sd_pixels_ = max_sd_pixels; max_sd_pixels_ = max_sd_pixels;
max_hd_pixels_ = max_hd_pixels;
max_uhd1_pixels_ = max_uhd1_pixels;
clear_lead_in_seconds_ = clear_lead_in_seconds; clear_lead_in_seconds_ = clear_lead_in_seconds;
crypto_period_duration_in_seconds_ = crypto_period_duration_in_seconds; crypto_period_duration_in_seconds_ = crypto_period_duration_in_seconds;
protection_scheme_ = protection_scheme; protection_scheme_ = protection_scheme;

View File

@ -35,12 +35,23 @@ class Muxer {
explicit Muxer(const MuxerOptions& options); explicit Muxer(const MuxerOptions& options);
virtual ~Muxer(); virtual ~Muxer();
// TODO(kqyang): refactor max_sd_pixels through crypto_period_duration into
// an encapsulated EncryptionParams structure.
/// Set encryption key source. /// Set encryption key source.
/// @param encryption_key_source points to the encryption key source. The /// @param encryption_key_source points to the encryption key source. The
/// caller retains ownership, and should not be NULL. /// caller retains ownership, and should not be NULL.
/// @param max_sd_pixels specifies the threshold to determine whether a video /// @param max_sd_pixels specifies the threshold to determine whether a video
/// track should be considered as SD or HD. If the track has more /// track should be considered as SD. If the max pixels per frame is
/// pixels per frame than max_sd_pixels, it is HD, SD otherwise. /// no higher than max_sd_pixels, it is SD.
/// @param max_hd_pixels specifies the threshold to determine whether a video
/// track should be considered as HD. If the max pixels per frame is
/// higher than max_sd_pixels, but no higher than max_hd_pixels,
/// it is HD.
/// @param max_uhd1_pixels specifies the threshold to determine whether a video
/// track should be considered as UHD1. If the max pixels per frame is
/// higher than max_hd_pixels, but no higher than max_uhd1_pixels,
/// it is UHD1. Otherwise it is UHD2.
/// @param clear_lead_in_seconds specifies clear lead duration in seconds. /// @param clear_lead_in_seconds specifies clear lead duration in seconds.
/// @param crypto_period_duration_in_seconds specifies crypto period duration /// @param crypto_period_duration_in_seconds specifies crypto period duration
/// in seconds. A positive value means key rotation is enabled, the /// in seconds. A positive value means key rotation is enabled, the
@ -49,6 +60,8 @@ class Muxer {
/// 'cbc1', 'cbcs'. /// 'cbc1', 'cbcs'.
void SetKeySource(KeySource* encryption_key_source, void SetKeySource(KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds, double clear_lead_in_seconds,
double crypto_period_duration_in_seconds, double crypto_period_duration_in_seconds,
FourCC protection_scheme); FourCC protection_scheme);
@ -89,6 +102,8 @@ class Muxer {
return encryption_key_source_; return encryption_key_source_;
} }
uint32_t max_sd_pixels() const { return max_sd_pixels_; } uint32_t max_sd_pixels() const { return max_sd_pixels_; }
uint32_t max_hd_pixels() const { return max_hd_pixels_; }
uint32_t max_uhd1_pixels() const { return max_uhd1_pixels_; }
double clear_lead_in_seconds() const { return clear_lead_in_seconds_; } double clear_lead_in_seconds() const { return clear_lead_in_seconds_; }
double crypto_period_duration_in_seconds() const { double crypto_period_duration_in_seconds() const {
return crypto_period_duration_in_seconds_; return crypto_period_duration_in_seconds_;
@ -120,6 +135,8 @@ class Muxer {
std::vector<MediaStream*> streams_; std::vector<MediaStream*> streams_;
KeySource* encryption_key_source_; KeySource* encryption_key_source_;
uint32_t max_sd_pixels_; uint32_t max_sd_pixels_;
uint32_t max_hd_pixels_;
uint32_t max_uhd1_pixels_;
double clear_lead_in_seconds_; double clear_lead_in_seconds_;
double crypto_period_duration_in_seconds_; double crypto_period_duration_in_seconds_;
FourCC protection_scheme_; FourCC protection_scheme_;

View File

@ -155,7 +155,9 @@ std::string GetSegmentName(const std::string& segment_template,
} }
KeySource::TrackType GetTrackTypeForEncryption(const StreamInfo& stream_info, KeySource::TrackType GetTrackTypeForEncryption(const StreamInfo& stream_info,
uint32_t max_sd_pixels) { uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels) {
if (stream_info.stream_type() == kStreamAudio) if (stream_info.stream_type() == kStreamAudio)
return KeySource::TRACK_TYPE_AUDIO; return KeySource::TRACK_TYPE_AUDIO;
@ -166,8 +168,14 @@ KeySource::TrackType GetTrackTypeForEncryption(const StreamInfo& stream_info,
const VideoStreamInfo& video_stream_info = const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(stream_info); static_cast<const VideoStreamInfo&>(stream_info);
uint32_t pixels = video_stream_info.width() * video_stream_info.height(); uint32_t pixels = video_stream_info.width() * video_stream_info.height();
return (pixels > max_sd_pixels) ? KeySource::TRACK_TYPE_HD if (pixels > max_uhd1_pixels) {
: KeySource::TRACK_TYPE_SD; return KeySource::TRACK_TYPE_UHD2;
} else if (pixels > max_hd_pixels) {
return KeySource::TRACK_TYPE_UHD1;
} else if (pixels > max_sd_pixels) {
return KeySource::TRACK_TYPE_HD;
}
return KeySource::TRACK_TYPE_SD;
} }
} // namespace media } // namespace media

View File

@ -42,10 +42,14 @@ std::string GetSegmentName(const std::string& segment_template,
/// Determine the track type for encryption from input. /// Determine the track type for encryption from input.
/// @param stream_info is the info of the stream. /// @param stream_info is the info of the stream.
/// @param max_sd_pixels is the maximum number of pixels to be considered SD. /// @param max_sd_pixels is the maximum number of pixels to be considered SD.
/// Anything above is HD. /// @param max_hd_pixels is the maximum number of pixels to be considered HD.
/// @param max_uhd1_pixels is the maximum number of pixels to be considered UHD1.
/// Anything above is UHD2.
/// @return track type for encryption. /// @return track type for encryption.
KeySource::TrackType GetTrackTypeForEncryption(const StreamInfo& stream_info, KeySource::TrackType GetTrackTypeForEncryption(const StreamInfo& stream_info,
uint32_t max_sd_pixels); uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels);
} // namespace media } // namespace media
} // namespace shaka } // namespace shaka

View File

@ -401,6 +401,12 @@ void WidevineKeySource::FillRequest(bool enable_key_rotation,
base::DictionaryValue* track_hd = new base::DictionaryValue(); base::DictionaryValue* track_hd = new base::DictionaryValue();
track_hd->SetString("type", "HD"); track_hd->SetString("type", "HD");
tracks->Append(track_hd); tracks->Append(track_hd);
base::DictionaryValue* track_uhd1 = new base::DictionaryValue();
track_uhd1->SetString("type", "UHD1");
tracks->Append(track_uhd1);
base::DictionaryValue* track_uhd2 = new base::DictionaryValue();
track_uhd2->SetString("type", "UHD2");
tracks->Append(track_uhd2);
base::DictionaryValue* track_audio = new base::DictionaryValue(); base::DictionaryValue* track_audio = new base::DictionaryValue();
track_audio->SetString("type", "AUDIO"); track_audio->SetString("type", "AUDIO");
tracks->Append(track_audio); tracks->Append(track_audio);

View File

@ -45,13 +45,16 @@ const char kLicenseStatusUnknownError[] = "UNKNOWN_ERROR";
const char kExpectedRequestMessageFormat[] = const char kExpectedRequestMessageFormat[] =
"{\"content_id\":\"%s\",\"drm_types\":[\"WIDEVINE\"],\"policy\":\"%s\"," "{\"content_id\":\"%s\",\"drm_types\":[\"WIDEVINE\"],\"policy\":\"%s\","
"\"tracks\":[{\"type\":\"SD\"},{\"type\":\"HD\"},{\"type\":\"AUDIO\"}]}"; "\"tracks\":[{\"type\":\"SD\"},{\"type\":\"HD\"},{\"type\":\"UHD1\"},"
"{\"type\":\"UHD2\"},{\"type\":\"AUDIO\"}]}";
const char kExpectedRequestMessageWithAssetIdFormat[] = const char kExpectedRequestMessageWithAssetIdFormat[] =
"{\"asset_id\":%u,\"drm_types\":[\"WIDEVINE\"]," "{\"asset_id\":%u,\"drm_types\":[\"WIDEVINE\"],"
"\"tracks\":[{\"type\":\"SD\"},{\"type\":\"HD\"},{\"type\":\"AUDIO\"}]}"; "\"tracks\":[{\"type\":\"SD\"},{\"type\":\"HD\"},{\"type\":\"UHD1\"},"
"{\"type\":\"UHD2\"},{\"type\":\"AUDIO\"}]}";
const char kExpectedRequestMessageWithPsshFormat[] = const char kExpectedRequestMessageWithPsshFormat[] =
"{\"drm_types\":[\"WIDEVINE\"],\"pssh_data\":\"%s\"," "{\"drm_types\":[\"WIDEVINE\"],\"pssh_data\":\"%s\","
"\"tracks\":[{\"type\":\"SD\"},{\"type\":\"HD\"},{\"type\":\"AUDIO\"}]}"; "\"tracks\":[{\"type\":\"SD\"},{\"type\":\"HD\"},{\"type\":\"UHD1\"},"
"{\"type\":\"UHD2\"},{\"type\":\"AUDIO\"}]}";
const char kExpectedSignedMessageFormat[] = const char kExpectedSignedMessageFormat[] =
"{\"request\":\"%s\",\"signature\":\"%s\",\"signer\":\"%s\"}"; "{\"request\":\"%s\",\"signature\":\"%s\",\"signer\":\"%s\"}";
const char kTrackFormat[] = const char kTrackFormat[] =
@ -99,9 +102,9 @@ std::string GetMockPsshData(const std::string& track_type) {
} }
std::string GenerateMockLicenseResponse() { std::string GenerateMockLicenseResponse() {
const std::string kTrackTypes[] = {"SD", "HD", "AUDIO"}; const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"};
std::string tracks; std::string tracks;
for (size_t i = 0; i < 3; ++i) { for (size_t i = 0; i < 5; ++i) {
if (!tracks.empty()) if (!tracks.empty())
tracks += ","; tracks += ",";
tracks += base::StringPrintf( tracks += base::StringPrintf(
@ -115,9 +118,9 @@ std::string GenerateMockLicenseResponse() {
} }
std::string GenerateMockClassicLicenseResponse() { std::string GenerateMockClassicLicenseResponse() {
const std::string kTrackTypes[] = {"SD", "HD", "AUDIO"}; const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"};
std::string tracks; std::string tracks;
for (size_t i = 0; i < 3; ++i) { for (size_t i = 0; i < 5; ++i) {
if (!tracks.empty()) if (!tracks.empty())
tracks += ","; tracks += ",";
tracks += base::StringPrintf( tracks += base::StringPrintf(
@ -180,7 +183,7 @@ class WidevineKeySourceTest : public ::testing::Test,
void VerifyKeys(bool classic) { void VerifyKeys(bool classic) {
EncryptionKey encryption_key; EncryptionKey encryption_key;
const std::string kTrackTypes[] = {"SD", "HD", "AUDIO"}; const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"};
for (size_t i = 0; i < arraysize(kTrackTypes); ++i) { for (size_t i = 0; i < arraysize(kTrackTypes); ++i) {
ASSERT_OK(widevine_key_source_->GetKey( ASSERT_OK(widevine_key_source_->GetKey(
KeySource::GetTrackTypeFromString(kTrackTypes[i]), KeySource::GetTrackTypeFromString(kTrackTypes[i]),
@ -228,6 +231,10 @@ TEST_P(WidevineKeySourceTest, GetTrackTypeFromString) {
KeySource::GetTrackTypeFromString("SD")); KeySource::GetTrackTypeFromString("SD"));
EXPECT_EQ(KeySource::TRACK_TYPE_HD, EXPECT_EQ(KeySource::TRACK_TYPE_HD,
KeySource::GetTrackTypeFromString("HD")); KeySource::GetTrackTypeFromString("HD"));
EXPECT_EQ(KeySource::TRACK_TYPE_UHD1,
KeySource::GetTrackTypeFromString("UHD1"));
EXPECT_EQ(KeySource::TRACK_TYPE_UHD2,
KeySource::GetTrackTypeFromString("UHD2"));
EXPECT_EQ(KeySource::TRACK_TYPE_AUDIO, EXPECT_EQ(KeySource::TRACK_TYPE_AUDIO,
KeySource::GetTrackTypeFromString("AUDIO")); KeySource::GetTrackTypeFromString("AUDIO"));
EXPECT_EQ(KeySource::TRACK_TYPE_UNKNOWN, EXPECT_EQ(KeySource::TRACK_TYPE_UNKNOWN,
@ -413,7 +420,8 @@ namespace {
const char kCryptoPeriodRequestMessageFormat[] = const char kCryptoPeriodRequestMessageFormat[] =
"{\"content_id\":\"%s\",\"crypto_period_count\":%u,\"drm_types\":[" "{\"content_id\":\"%s\",\"crypto_period_count\":%u,\"drm_types\":["
"\"WIDEVINE\"],\"first_crypto_period_index\":%u,\"policy\":\"%s\"," "\"WIDEVINE\"],\"first_crypto_period_index\":%u,\"policy\":\"%s\","
"\"tracks\":[{\"type\":\"SD\"},{\"type\":\"HD\"},{\"type\":\"AUDIO\"}]}"; "\"tracks\":[{\"type\":\"SD\"},{\"type\":\"HD\"},{\"type\":\"UHD1\"},"
"{\"type\":\"UHD2\"},{\"type\":\"AUDIO\"}]}";
const char kCryptoPeriodTrackFormat[] = const char kCryptoPeriodTrackFormat[] =
"{\"type\":\"%s\",\"key_id\":\"%s\",\"key\":" "{\"type\":\"%s\",\"key_id\":\"%s\",\"key\":"
@ -427,12 +435,12 @@ std::string GetMockKey(const std::string& track_type, uint32_t index) {
std::string GenerateMockKeyRotationLicenseResponse( std::string GenerateMockKeyRotationLicenseResponse(
uint32_t initial_crypto_period_index, uint32_t initial_crypto_period_index,
uint32_t crypto_period_count) { uint32_t crypto_period_count) {
const std::string kTrackTypes[] = {"SD", "HD", "AUDIO"}; const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"};
std::string tracks; std::string tracks;
for (uint32_t index = initial_crypto_period_index; for (uint32_t index = initial_crypto_period_index;
index < initial_crypto_period_index + crypto_period_count; index < initial_crypto_period_index + crypto_period_count;
++index) { ++index) {
for (size_t i = 0; i < 3; ++i) { for (size_t i = 0; i < 5; ++i) {
if (!tracks.empty()) if (!tracks.empty())
tracks += ","; tracks += ",";
tracks += base::StringPrintf( tracks += base::StringPrintf(
@ -495,8 +503,8 @@ TEST_P(WidevineKeySourceTest, KeyRotationTest) {
EncryptionKey encryption_key; EncryptionKey encryption_key;
for (size_t i = 0; i < arraysize(kCryptoPeriodIndexes); ++i) { for (size_t i = 0; i < arraysize(kCryptoPeriodIndexes); ++i) {
const std::string kTrackTypes[] = {"SD", "HD", "AUDIO"}; const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"};
for (size_t j = 0; j < 3; ++j) { for (size_t j = 0; j < 5; ++j) {
ASSERT_OK(widevine_key_source_->GetCryptoPeriodKey( ASSERT_OK(widevine_key_source_->GetCryptoPeriodKey(
kCryptoPeriodIndexes[i], kCryptoPeriodIndexes[i],
KeySource::GetTrackTypeFromString(kTrackTypes[j]), KeySource::GetTrackTypeFromString(kTrackTypes[j]),

View File

@ -24,7 +24,8 @@ Status TsMuxer::Initialize() {
segmenter_.reset(new TsSegmenter(options(), muxer_listener())); segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
Status status = Status status =
segmenter_->Initialize(*streams()[0]->info(), encryption_key_source(), segmenter_->Initialize(*streams()[0]->info(), encryption_key_source(),
max_sd_pixels(), clear_lead_in_seconds()); max_sd_pixels(), max_hd_pixels(),
max_uhd1_pixels(), clear_lead_in_seconds());
FireOnMediaStartEvent(); FireOnMediaStartEvent();
return status; return status;
} }

View File

@ -34,6 +34,8 @@ TsSegmenter::~TsSegmenter() {}
Status TsSegmenter::Initialize(const StreamInfo& stream_info, Status TsSegmenter::Initialize(const StreamInfo& stream_info,
KeySource* encryption_key_source, KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds) { double clear_lead_in_seconds) {
if (muxer_options_.segment_template.empty()) if (muxer_options_.segment_template.empty())
return Status(error::MUXER_FAILURE, "Segment template not specified."); return Status(error::MUXER_FAILURE, "Segment template not specified.");
@ -47,7 +49,8 @@ Status TsSegmenter::Initialize(const StreamInfo& stream_info,
if (encryption_key_source) { if (encryption_key_source) {
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey()); std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
const KeySource::TrackType type = const KeySource::TrackType type =
GetTrackTypeForEncryption(stream_info, max_sd_pixels); GetTrackTypeForEncryption(stream_info, max_sd_pixels,
max_hd_pixels, max_uhd1_pixels);
Status status = encryption_key_source->GetKey(type, encryption_key.get()); Status status = encryption_key_source->GetKey(type, encryption_key.get());
if (encryption_key->iv.empty()) { if (encryption_key->iv.empty()) {

View File

@ -43,6 +43,8 @@ class TsSegmenter {
Status Initialize(const StreamInfo& stream_info, Status Initialize(const StreamInfo& stream_info,
KeySource* encryption_key_source, KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds); double clear_lead_in_seconds);
/// Finalize the segmenter. /// Finalize the segmenter.

View File

@ -124,7 +124,7 @@ TEST_F(TsSegmenterTest, Initialize) {
segmenter.InjectPesPacketGeneratorForTesting( segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_)); std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0)); EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
} }
TEST_F(TsSegmenterTest, AddSample) { TEST_F(TsSegmenterTest, AddSample) {
@ -171,7 +171,7 @@ TEST_F(TsSegmenterTest, AddSample) {
segmenter.InjectPesPacketGeneratorForTesting( segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_)); std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0)); EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
EXPECT_OK(segmenter.AddSample(sample)); EXPECT_OK(segmenter.AddSample(sample));
} }
@ -275,7 +275,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_)); segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting( segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_)); std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0)); EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
EXPECT_OK(segmenter.AddSample(sample1)); EXPECT_OK(segmenter.AddSample(sample1));
EXPECT_OK(segmenter.AddSample(sample2)); EXPECT_OK(segmenter.AddSample(sample2));
} }
@ -302,7 +302,7 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) {
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_)); segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting( segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_)); std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0)); EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
EXPECT_OK(segmenter.Finalize()); EXPECT_OK(segmenter.Finalize());
} }
@ -333,7 +333,7 @@ TEST_F(TsSegmenterTest, Finalize) {
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_)); segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting( segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_)); std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0)); EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
segmenter.SetTsWriterFileOpenedForTesting(true); segmenter.SetTsWriterFileOpenedForTesting(true);
EXPECT_OK(segmenter.Finalize()); EXPECT_OK(segmenter.Finalize());
} }
@ -439,7 +439,7 @@ TEST_F(TsSegmenterTest, SegmentOnlyBeforeKeyFrame) {
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_)); segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting( segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_)); std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0)); EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
EXPECT_OK(segmenter.AddSample(key_frame_sample1)); EXPECT_OK(segmenter.AddSample(key_frame_sample1));
EXPECT_OK(segmenter.AddSample(non_key_frame_sample)); EXPECT_OK(segmenter.AddSample(non_key_frame_sample));
EXPECT_OK(segmenter.AddSample(key_frame_sample2)); EXPECT_OK(segmenter.AddSample(key_frame_sample2));
@ -475,11 +475,14 @@ TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) {
.WillOnce(Return(Status::OK)); .WillOnce(Return(Status::OK));
const uint32_t k480pPixels = 640 * 480; const uint32_t k480pPixels = 640 * 480;
const uint32_t k1080pPixels = 1920 * 1080;
const uint32_t k2160pPixels = 4096 * 2160;
// Set this to 0 so that Finalize will call // Set this to 0 so that Finalize will call
// PesPacketGenerator::SetEncryptionKey(). // PesPacketGenerator::SetEncryptionKey().
// Even tho no samples have been added. // Even tho no samples have been added.
const double kClearLeadSeconds = 0; const double kClearLeadSeconds = 0;
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, k480pPixels, EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, k480pPixels,
k1080pPixels, k2160pPixels,
kClearLeadSeconds)); kClearLeadSeconds));
} }
@ -513,11 +516,14 @@ TEST_F(TsSegmenterTest, WithEncryptionNoClearLeadNoMuxerListener) {
.WillOnce(Return(Status::OK)); .WillOnce(Return(Status::OK));
const uint32_t k480pPixels = 640 * 480; const uint32_t k480pPixels = 640 * 480;
const uint32_t k1080pPixels = 1920 * 1080;
const uint32_t k2160pPixels = 4096 * 2160;
// Set this to 0 so that Finalize will call // Set this to 0 so that Finalize will call
// PesPacketGenerator::SetEncryptionKey(). // PesPacketGenerator::SetEncryptionKey().
// Even tho no samples have been added. // Even tho no samples have been added.
const double kClearLeadSeconds = 0; const double kClearLeadSeconds = 0;
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, k480pPixels, EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, k480pPixels,
k1080pPixels, k2160pPixels,
kClearLeadSeconds)); kClearLeadSeconds));
} }
@ -530,6 +536,8 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
MuxerOptions options; MuxerOptions options;
options.segment_duration = 1.0; options.segment_duration = 1.0;
const uint32_t k1080pPixels = 1920 * 1080;
const uint32_t k2160pPixels = 4096 * 2160;
const double kClearLeadSeconds = 1.0; const double kClearLeadSeconds = 1.0;
options.segment_template = "file$Number$.ts"; options.segment_template = "file$Number$.ts";
@ -610,6 +618,7 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
EXPECT_CALL(mock_listener, OnEncryptionInfoReady(_, _, _, _, _)); EXPECT_CALL(mock_listener, OnEncryptionInfoReady(_, _, _, _, _));
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, 0, EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, 0,
k1080pPixels, k2160pPixels,
kClearLeadSeconds)); kClearLeadSeconds));
EXPECT_OK(segmenter.AddSample(sample1)); EXPECT_OK(segmenter.AddSample(sample1));

View File

@ -144,8 +144,9 @@ Status MP4Muxer::Initialize() {
const Status segmenter_initialized = segmenter_->Initialize( const Status segmenter_initialized = segmenter_->Initialize(
streams(), muxer_listener(), progress_listener(), encryption_key_source(), streams(), muxer_listener(), progress_listener(), encryption_key_source(),
max_sd_pixels(), clear_lead_in_seconds(), max_sd_pixels(), max_hd_pixels(), max_uhd1_pixels(),
crypto_period_duration_in_seconds(), protection_scheme()); clear_lead_in_seconds(), crypto_period_duration_in_seconds(),
protection_scheme());
if (!segmenter_initialized.ok()) if (!segmenter_initialized.ok())
return segmenter_initialized; return segmenter_initialized;

View File

@ -167,6 +167,8 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
ProgressListener* progress_listener, ProgressListener* progress_listener,
KeySource* encryption_key_source, KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds, double clear_lead_in_seconds,
double crypto_period_duration_in_seconds, double crypto_period_duration_in_seconds,
FourCC protection_scheme) { FourCC protection_scheme) {
@ -196,7 +198,8 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
} }
KeySource::TrackType track_type = KeySource::TrackType track_type =
GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels); GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels,
max_hd_pixels, max_uhd1_pixels);
SampleDescription& description = SampleDescription& description =
moov_->tracks[i].media.information.sample_table.description; moov_->tracks[i].media.information.sample_table.description;
ProtectionPattern pattern = ProtectionPattern pattern =

View File

@ -55,8 +55,16 @@ class Segmenter {
/// the encryption keys. It can be NULL to indicate that no encryption /// the encryption keys. It can be NULL to indicate that no encryption
/// is required. /// is required.
/// @param max_sd_pixels specifies the threshold to determine whether a video /// @param max_sd_pixels specifies the threshold to determine whether a video
/// track should be considered as SD or HD. If the track has more /// track should be considered as SD. If the max pixels per frame is
/// pixels per frame than max_sd_pixels, it is HD, SD otherwise. /// no higher than max_sd_pixels, it is SD.
/// @param max_hd_pixels specifies the threshold to determine whether a video
/// track should be considered as HD. If the max pixels per frame is
/// higher than max_sd_pixels, but no higher than max_hd_pixels,
/// it is HD.
/// @param max_uhd1_pixels specifies the threshold to determine whether a video
/// track should be considered as UHD1. If the max pixels per frame is
/// higher than max_hd_pixels, but no higher than max_uhd1_pixels,
/// it is UHD1. Otherwise it is UHD2.
/// @param clear_time specifies clear lead duration in seconds. /// @param clear_time specifies clear lead duration in seconds.
/// @param crypto_period_duration specifies crypto period duration in seconds. /// @param crypto_period_duration specifies crypto period duration in seconds.
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens', /// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
@ -67,6 +75,8 @@ class Segmenter {
ProgressListener* progress_listener, ProgressListener* progress_listener,
KeySource* encryption_key_source, KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds, double clear_lead_in_seconds,
double crypto_period_duration_in_seconds, double crypto_period_duration_in_seconds,
FourCC protection_scheme); FourCC protection_scheme);

View File

@ -54,6 +54,8 @@ Status Segmenter::Initialize(std::unique_ptr<MkvWriter> writer,
MuxerListener* muxer_listener, MuxerListener* muxer_listener,
KeySource* encryption_key_source, KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds) { double clear_lead_in_seconds) {
muxer_listener_ = muxer_listener; muxer_listener_ = muxer_listener;
info_ = info; info_ = info;
@ -82,7 +84,9 @@ Status Segmenter::Initialize(std::unique_ptr<MkvWriter> writer,
Status status; Status status;
if (encryption_key_source) { if (encryption_key_source) {
status = InitializeEncryptor(encryption_key_source, status = InitializeEncryptor(encryption_key_source,
max_sd_pixels); max_sd_pixels,
max_hd_pixels,
max_uhd1_pixels);
if (!status.ok()) if (!status.ok())
return status; return status;
} }
@ -366,10 +370,13 @@ Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
} }
Status Segmenter::InitializeEncryptor(KeySource* key_source, Status Segmenter::InitializeEncryptor(KeySource* key_source,
uint32_t max_sd_pixels) { uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels) {
encryptor_.reset(new Encryptor()); encryptor_.reset(new Encryptor());
const KeySource::TrackType track_type = const KeySource::TrackType track_type =
GetTrackTypeForEncryption(*info_, max_sd_pixels); GetTrackTypeForEncryption(*info_, max_sd_pixels, max_hd_pixels,
max_uhd1_pixels);
if (track_type == KeySource::TrackType::TRACK_TYPE_UNKNOWN) if (track_type == KeySource::TrackType::TRACK_TYPE_UNKNOWN)
return Status::OK; return Status::OK;
return encryptor_->Initialize(muxer_listener_, track_type, info_->codec(), return encryptor_->Initialize(muxer_listener_, track_type, info_->codec(),

View File

@ -46,8 +46,16 @@ class Segmenter {
/// the encryption keys. It can be NULL to indicate that no encryption /// the encryption keys. It can be NULL to indicate that no encryption
/// is required. /// is required.
/// @param max_sd_pixels specifies the threshold to determine whether a video /// @param max_sd_pixels specifies the threshold to determine whether a video
/// track should be considered as SD or HD. If the track has more /// track should be considered as SD. If the max pixels per frame is
/// pixels per frame than max_sd_pixels, it is HD, SD otherwise. /// no higher than max_sd_pixels, it is SD.
/// @param max_hd_pixels specifies the threshold to determine whether a video
/// track should be considered as HD. If the max pixels per frame is
/// higher than max_sd_pixels, but no higher than max_hd_pixels,
/// it is HD.
/// @param max_uhd1_pixels specifies the threshold to determine whether a video
/// track should be considered as UHD1. If the max pixels per frame is
/// higher than max_hd_pixels, but no higher than max_uhd1_pixels,
/// it is UHD1. Otherwise it is UHD2.
/// @param clear_time specifies clear lead duration in seconds. /// @param clear_time specifies clear lead duration in seconds.
/// @return OK on success, an error status otherwise. /// @return OK on success, an error status otherwise.
Status Initialize(std::unique_ptr<MkvWriter> writer, Status Initialize(std::unique_ptr<MkvWriter> writer,
@ -56,6 +64,8 @@ class Segmenter {
MuxerListener* muxer_listener, MuxerListener* muxer_listener,
KeySource* encryption_key_source, KeySource* encryption_key_source,
uint32_t max_sd_pixels, uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds); double clear_lead_in_seconds);
/// Finalize the segmenter. /// Finalize the segmenter.
@ -114,7 +124,8 @@ class Segmenter {
private: private:
Status CreateVideoTrack(VideoStreamInfo* info); Status CreateVideoTrack(VideoStreamInfo* info);
Status CreateAudioTrack(AudioStreamInfo* info); Status CreateAudioTrack(AudioStreamInfo* info);
Status InitializeEncryptor(KeySource* key_source, uint32_t max_sd_pixels); Status InitializeEncryptor(KeySource* key_source, uint32_t max_sd_pixels,
uint32_t max_hd_pixels, uint32_t max_uhd1_pixels);
// Writes the previous frame to the file. // Writes the previous frame to the file.
Status WriteFrame(bool write_duration); Status WriteFrame(bool write_duration);

View File

@ -56,6 +56,7 @@ class SegmentTestBase : public ::testing::Test {
ASSERT_OK(segmenter->Initialize( ASSERT_OK(segmenter->Initialize(
std::move(writer), info, NULL /* progress_listener */, std::move(writer), info, NULL /* progress_listener */,
NULL /* muxer_listener */, key_source, 0 /* max_sd_pixels */, NULL /* muxer_listener */, key_source, 0 /* max_sd_pixels */,
0 /* max_hd_pixels */, 0 /* max_uhd1_pixels */,
1 /* clear_lead_in_seconds */)); 1 /* clear_lead_in_seconds */));
*result = std::move(segmenter); *result = std::move(segmenter);
} }

View File

@ -52,7 +52,7 @@ Status WebMMuxer::Initialize() {
Status initialized = segmenter_->Initialize( Status initialized = segmenter_->Initialize(
std::move(writer), streams()[0]->info().get(), progress_listener(), std::move(writer), streams()[0]->info().get(), progress_listener(),
muxer_listener(), encryption_key_source(), max_sd_pixels(), muxer_listener(), encryption_key_source(), max_sd_pixels(),
clear_lead_in_seconds()); max_hd_pixels(), max_uhd1_pixels(), clear_lead_in_seconds());
if (!initialized.ok()) if (!initialized.ok())
return initialized; return initialized;

View File

@ -15,6 +15,7 @@
#include "packager/media/base/fourccs.h" #include "packager/media/base/fourccs.h"
#include "packager/media/base/media_stream.h" #include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer.h" #include "packager/media/base/muxer.h"
#include "packager/media/base/muxer_util.h"
#include "packager/media/base/stream_info.h" #include "packager/media/base/stream_info.h"
#include "packager/media/base/test/status_test_util.h" #include "packager/media/base/test/status_test_util.h"
#include "packager/media/formats/mp4/mp4_muxer.h" #include "packager/media/formats/mp4/mp4_muxer.h"
@ -57,6 +58,11 @@ const char kKeyHex[] = "6fc96fe628a265b13aeddec0bc421f4d";
const double kClearLeadInSeconds = 1.5; const double kClearLeadInSeconds = 1.5;
const double kCryptoDurationInSeconds = 0; // Key rotation is disabled. const double kCryptoDurationInSeconds = 0; // Key rotation is disabled.
// Track resolution constants.
const uint32_t kMaxSDPixels = 640 * 480;
const uint32_t kMaxHDPixels = 1920 * 1080;
const uint32_t kMaxUHD1Pixels = 4096 * 2160;
MediaStream* FindFirstStreamOfType( MediaStream* FindFirstStreamOfType(
const std::vector<std::unique_ptr<MediaStream>>& streams, const std::vector<std::unique_ptr<MediaStream>>& streams,
StreamType stream_type) { StreamType stream_type) {
@ -178,7 +184,8 @@ void PackagerTestBasic::Remux(const std::string& input,
if (enable_encryption) { if (enable_encryption) {
muxer_video->SetKeySource(encryption_key_source.get(), muxer_video->SetKeySource(encryption_key_source.get(),
KeySource::TRACK_TYPE_SD, kClearLeadInSeconds, kMaxSDPixels, kMaxHDPixels,
kMaxUHD1Pixels, kClearLeadInSeconds,
kCryptoDurationInSeconds, FOURCC_cenc); kCryptoDurationInSeconds, FOURCC_cenc);
} }
} }
@ -198,7 +205,8 @@ void PackagerTestBasic::Remux(const std::string& input,
if (enable_encryption) { if (enable_encryption) {
muxer_audio->SetKeySource(encryption_key_source.get(), muxer_audio->SetKeySource(encryption_key_source.get(),
KeySource::TRACK_TYPE_SD, kClearLeadInSeconds, kMaxSDPixels, kMaxHDPixels,
kMaxUHD1Pixels, kClearLeadInSeconds,
kCryptoDurationInSeconds, FOURCC_cenc); kCryptoDurationInSeconds, FOURCC_cenc);
} }
} }
@ -314,6 +322,39 @@ TEST_P(PackagerTestBasic, MP4MuxerLanguageWithSubtag) {
ASSERT_EQ("por", stream->info()->language()); ASSERT_EQ("por", stream->info()->language());
} }
TEST_P(PackagerTestBasic, GetTrackTypeForEncryption) {
Demuxer demuxer(GetFullPath(GetParam()));
ASSERT_OK(demuxer.Initialize());
MediaStream* video_stream = FindFirstVideoStream(demuxer.streams());
MediaStream* audio_stream = FindFirstAudioStream(demuxer.streams());
// Typical resolution constraints should set the resolution in the SD range
KeySource::TrackType track_type = GetTrackTypeForEncryption(
*video_stream->info(), kMaxSDPixels, kMaxHDPixels, kMaxUHD1Pixels);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("SD"), track_type);
// Setting the max SD value to 1 should set the resolution in the HD range
track_type = GetTrackTypeForEncryption(
*video_stream->info(), 1, kMaxHDPixels, kMaxUHD1Pixels);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("HD"), track_type);
// Setting the max HD value to 2 should set the resolution in the UHD1 range
track_type = GetTrackTypeForEncryption(
*video_stream->info(), 1, 2, kMaxUHD1Pixels);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("UHD1"), track_type);
// Setting the max UHD1 value to 3 should set the resolution in the UHD2 range
track_type = GetTrackTypeForEncryption(
*video_stream->info(), 1, 2, 3);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("UHD2"), track_type);
// Audio stream should always set the track_type to AUDIO
track_type = GetTrackTypeForEncryption(
*audio_stream->info(), kMaxSDPixels, kMaxHDPixels, kMaxUHD1Pixels);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("AUDIO"), track_type);
}
class PackagerTest : public PackagerTestBasic { class PackagerTest : public PackagerTestBasic {
public: public:
void SetUp() override { void SetUp() override {