shaka-packager/packager/media/test/packager_test.cc

427 lines
16 KiB
C++

// Copyright 2014 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 <gtest/gtest.h>
#include "packager/base/files/file_util.h"
#include "packager/base/strings/string_number_conversions.h"
#include "packager/base/strings/stringprintf.h"
#include "packager/base/time/clock.h"
#include "packager/media/base/demuxer.h"
#include "packager/media/base/encryption_modes.h"
#include "packager/media/base/fixed_key_source.h"
#include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer.h"
#include "packager/media/base/stream_info.h"
#include "packager/media/base/test/status_test_util.h"
#include "packager/media/formats/mp4/mp4_muxer.h"
#include "packager/media/test/test_data_util.h"
using ::testing::ValuesIn;
namespace edash_packager {
namespace media {
namespace {
const char* kMediaFiles[] = {"bear-640x360.mp4", "bear-640x360-av_frag.mp4",
"bear-1280x720.ts"};
// Muxer options.
const double kSegmentDurationInSeconds = 1.0;
const double kFragmentDurationInSecodns = 0.1;
const bool kSegmentSapAligned = true;
const bool kFragmentSapAligned = true;
const int kNumSubsegmentsPerSidx = 2;
const char kOutputVideo[] = "output_video";
const char kOutputVideo2[] = "output_video_2";
const char kOutputAudio[] = "output_audio";
const char kOutputAudio2[] = "output_audio_2";
const char kOutputNone[] = "";
const char kSegmentTemplate[] = "template$Number$.m4s";
const char kSegmentTemplateOutputPattern[] = "template%d.m4s";
const bool kSingleSegment = true;
const bool kMultipleSegments = false;
const bool kEnableEncryption = true;
const bool kDisableEncryption = false;
const char kNoLanguageOverride[] = "";
// Encryption constants.
const char kKeyIdHex[] = "e5007e6e9dcd5ac095202ed3758382cd";
const char kKeyHex[] = "6fc96fe628a265b13aeddec0bc421f4d";
const double kClearLeadInSeconds = 1.5;
const double kCryptoDurationInSeconds = 0; // Key rotation is disabled.
MediaStream* FindFirstStreamOfType(const std::vector<MediaStream*>& streams,
StreamType stream_type) {
typedef std::vector<MediaStream*>::const_iterator StreamIterator;
for (StreamIterator it = streams.begin(); it != streams.end(); ++it) {
if ((*it)->info()->stream_type() == stream_type)
return *it;
}
return NULL;
}
MediaStream* FindFirstVideoStream(const std::vector<MediaStream*>& streams) {
return FindFirstStreamOfType(streams, kStreamVideo);
}
MediaStream* FindFirstAudioStream(const std::vector<MediaStream*>& streams) {
return FindFirstStreamOfType(streams, kStreamAudio);
}
} // namespace
class FakeClock : public base::Clock {
public:
// Fake the clock to return NULL time.
base::Time Now() override { return base::Time(); }
};
class PackagerTestBasic : public ::testing::TestWithParam<const char*> {
public:
PackagerTestBasic() {}
void SetUp() override {
// Create a test directory for testing, will be deleted after test.
ASSERT_TRUE(base::CreateNewTempDirectory("packager_", &test_directory_));
// Copy the input to test directory for easy reference.
ASSERT_TRUE(base::CopyFile(GetTestDataFilePath(GetParam()),
test_directory_.AppendASCII(GetParam())));
}
void TearDown() override { base::DeleteFile(test_directory_, true); }
std::string GetFullPath(const std::string& file_name);
// Check if |file1| and |file2| are the same.
bool ContentsEqual(const std::string& file1, const std::string file2);
MuxerOptions SetupOptions(const std::string& output, bool single_segment);
void Remux(const std::string& input,
const std::string& video_output,
const std::string& audio_output,
bool single_segment,
bool enable_encryption,
const std::string& override_language);
void Decrypt(const std::string& input,
const std::string& video_output,
const std::string& audio_output);
protected:
base::FilePath test_directory_;
FakeClock fake_clock_;
};
std::string PackagerTestBasic::GetFullPath(const std::string& file_name) {
return test_directory_.AppendASCII(file_name).value();
}
bool PackagerTestBasic::ContentsEqual(const std::string& file1,
const std::string file2) {
return base::ContentsEqual(test_directory_.AppendASCII(file1),
test_directory_.AppendASCII(file2));
}
MuxerOptions PackagerTestBasic::SetupOptions(const std::string& output,
bool single_segment) {
MuxerOptions options;
options.single_segment = single_segment;
options.segment_duration = kSegmentDurationInSeconds;
options.fragment_duration = kFragmentDurationInSecodns;
options.segment_sap_aligned = kSegmentSapAligned;
options.fragment_sap_aligned = kFragmentSapAligned;
options.num_subsegments_per_sidx = kNumSubsegmentsPerSidx;
options.output_file_name = GetFullPath(output);
options.segment_template = GetFullPath(kSegmentTemplate);
options.temp_dir = test_directory_.value();
return options;
}
void PackagerTestBasic::Remux(const std::string& input,
const std::string& video_output,
const std::string& audio_output,
bool single_segment,
bool enable_encryption,
const std::string& language_override) {
CHECK(!video_output.empty() || !audio_output.empty());
Demuxer demuxer(GetFullPath(input));
ASSERT_OK(demuxer.Initialize());
scoped_ptr<KeySource> encryption_key_source(
FixedKeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", ""));
DCHECK(encryption_key_source);
scoped_ptr<Muxer> muxer_video;
if (!video_output.empty()) {
muxer_video.reset(
new mp4::MP4Muxer(SetupOptions(video_output, single_segment)));
muxer_video->set_clock(&fake_clock_);
MediaStream* stream = FindFirstVideoStream(demuxer.streams());
if (!language_override.empty()) {
stream->info()->set_language(language_override);
ASSERT_EQ(language_override, stream->info()->language());
}
muxer_video->AddStream(stream);
if (enable_encryption) {
muxer_video->SetKeySource(encryption_key_source.get(),
KeySource::TRACK_TYPE_SD,
kClearLeadInSeconds,
kCryptoDurationInSeconds,
kEncryptionModeAesCtr);
}
}
scoped_ptr<Muxer> muxer_audio;
if (!audio_output.empty()) {
muxer_audio.reset(
new mp4::MP4Muxer(SetupOptions(audio_output, single_segment)));
muxer_audio->set_clock(&fake_clock_);
MediaStream* stream = FindFirstAudioStream(demuxer.streams());
if (!language_override.empty()) {
stream->info()->set_language(language_override);
ASSERT_EQ(language_override, stream->info()->language());
}
muxer_audio->AddStream(stream);
if (enable_encryption) {
muxer_audio->SetKeySource(encryption_key_source.get(),
KeySource::TRACK_TYPE_SD,
kClearLeadInSeconds,
kCryptoDurationInSeconds,
kEncryptionModeAesCtr);
}
}
// Start remuxing process.
ASSERT_OK(demuxer.Run());
}
void PackagerTestBasic::Decrypt(const std::string& input,
const std::string& video_output,
const std::string& audio_output) {
CHECK(!video_output.empty() || !audio_output.empty());
Demuxer demuxer(GetFullPath(input));
scoped_ptr<KeySource> decryption_key_source(
FixedKeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", ""));
ASSERT_TRUE(decryption_key_source);
demuxer.SetKeySource(decryption_key_source.Pass());
ASSERT_OK(demuxer.Initialize());
scoped_ptr<Muxer> muxer;
MediaStream* stream(NULL);
if (!video_output.empty()) {
muxer.reset(
new mp4::MP4Muxer(SetupOptions(video_output, true)));
stream = FindFirstVideoStream(demuxer.streams());
}
if (!audio_output.empty()) {
muxer.reset(
new mp4::MP4Muxer(SetupOptions(audio_output, true)));
stream = FindFirstAudioStream(demuxer.streams());
}
ASSERT_TRUE(muxer);
ASSERT_TRUE(stream != NULL);
ASSERT_TRUE(stream->info()->is_encrypted());
muxer->set_clock(&fake_clock_);
muxer->AddStream(stream);
ASSERT_OK(demuxer.Run());
}
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedVideo) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputVideo,
kOutputNone,
kSingleSegment,
kDisableEncryption,
kNoLanguageOverride));
}
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedAudio) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputNone,
kOutputAudio,
kSingleSegment,
kDisableEncryption,
kNoLanguageOverride));
}
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedVideo) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputVideo,
kOutputNone,
kSingleSegment,
kEnableEncryption,
kNoLanguageOverride));
ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputVideo,
kOutputVideo2,
kOutputNone));
}
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedAudio) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputNone,
kOutputAudio,
kSingleSegment,
kEnableEncryption,
kNoLanguageOverride));
ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputAudio,
kOutputNone,
kOutputAudio2));
}
TEST_P(PackagerTestBasic, MP4MuxerLanguageWithoutSubtag) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputNone,
kOutputAudio,
kSingleSegment,
kDisableEncryption,
"por"));
Demuxer demuxer(GetFullPath(kOutputAudio));
ASSERT_OK(demuxer.Initialize());
MediaStream* stream = FindFirstAudioStream(demuxer.streams());
ASSERT_EQ("por", stream->info()->language());
}
TEST_P(PackagerTestBasic, MP4MuxerLanguageWithSubtag) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputNone,
kOutputAudio,
kSingleSegment,
kDisableEncryption,
"por-BR"));
Demuxer demuxer(GetFullPath(kOutputAudio));
ASSERT_OK(demuxer.Initialize());
MediaStream* stream = FindFirstAudioStream(demuxer.streams());
ASSERT_EQ("por", stream->info()->language());
}
class PackagerTest : public PackagerTestBasic {
public:
void SetUp() override {
PackagerTestBasic::SetUp();
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputVideo,
kOutputNone,
kSingleSegment,
kDisableEncryption,
kNoLanguageOverride));
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputNone,
kOutputAudio,
kSingleSegment,
kDisableEncryption,
kNoLanguageOverride));
}
};
TEST_P(PackagerTest, MP4MuxerSingleSegmentUnencryptedVideoAgain) {
// Take the muxer output and feed into muxer again. The new muxer output
// should contain the same contents as the previous muxer output.
ASSERT_NO_FATAL_FAILURE(Remux(kOutputVideo,
kOutputVideo2,
kOutputNone,
kSingleSegment,
kDisableEncryption,
kNoLanguageOverride));
EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2));
}
TEST_P(PackagerTest, MP4MuxerSingleSegmentUnencryptedAudioAgain) {
// Take the muxer output and feed into muxer again. The new muxer output
// should contain the same contents as the previous muxer output.
ASSERT_NO_FATAL_FAILURE(Remux(kOutputAudio,
kOutputNone,
kOutputAudio2,
kSingleSegment,
kDisableEncryption,
kNoLanguageOverride));
EXPECT_TRUE(ContentsEqual(kOutputAudio, kOutputAudio2));
}
TEST_P(PackagerTest, MP4MuxerSingleSegmentUnencryptedSeparateAudioVideo) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputVideo2,
kOutputAudio2,
kSingleSegment,
kDisableEncryption,
kNoLanguageOverride));
// Compare the output with single muxer output. They should match.
EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2));
EXPECT_TRUE(ContentsEqual(kOutputAudio, kOutputAudio2));
}
TEST_P(PackagerTest, MP4MuxerMultiSegmentsUnencryptedVideo) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputVideo2,
kOutputNone,
kMultipleSegments,
kDisableEncryption,
kNoLanguageOverride));
// Find and concatenates the segments.
const std::string kOutputVideoSegmentsCombined =
std::string(kOutputVideo) + "_combined";
base::FilePath output_path =
test_directory_.AppendASCII(kOutputVideoSegmentsCombined);
ASSERT_TRUE(
base::CopyFile(test_directory_.AppendASCII(kOutputVideo2), output_path));
const int kStartSegmentIndex = 1; // start from one.
int segment_index = kStartSegmentIndex;
while (true) {
base::FilePath segment_path = test_directory_.AppendASCII(
base::StringPrintf(kSegmentTemplateOutputPattern, segment_index));
if (!base::PathExists(segment_path))
break;
std::string segment_content;
ASSERT_TRUE(base::ReadFileToString(segment_path, &segment_content));
EXPECT_TRUE(base::AppendToFile(output_path, segment_content.data(),
segment_content.size()));
++segment_index;
}
// We should have at least one segment.
ASSERT_LT(kStartSegmentIndex, segment_index);
// Feed the combined file into muxer again. The new muxer output should be
// the same as by just feeding the input to muxer.
ASSERT_NO_FATAL_FAILURE(Remux(kOutputVideoSegmentsCombined,
kOutputVideo2,
kOutputNone,
kSingleSegment,
kDisableEncryption,
kNoLanguageOverride));
EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2));
}
INSTANTIATE_TEST_CASE_P(PackagerEndToEnd,
PackagerTestBasic,
ValuesIn(kMediaFiles));
INSTANTIATE_TEST_CASE_P(PackagerEndToEnd, PackagerTest, ValuesIn(kMediaFiles));
} // namespace media
} // namespace edash_packager