blob: 95bc1a7bffcf854b780a9b3d252a5af21200be75 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/test/video_decode_accelerator_unittest_helpers.h"
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/files/file_util.h"
#include "base/message_loop/message_pump_type.h"
#include "base/strings/string_split.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/video_decoder_config.h"
#include "media/gpu/macros.h"
#include "media/gpu/test/rendering_helper.h"
#include "media/video/h264_parser.h"
#if defined(OS_CHROMEOS)
#include "ui/ozone/public/ozone_gpu_test_helper.h"
#endif
namespace media {
namespace test {
namespace {
const size_t kMD5StringLength = 32;
} // namespace
VideoDecodeAcceleratorTestEnvironment::VideoDecodeAcceleratorTestEnvironment(
bool use_gl_renderer)
: use_gl_renderer_(use_gl_renderer),
rendering_thread_("GLRenderingVDAClientThread") {}
VideoDecodeAcceleratorTestEnvironment::
~VideoDecodeAcceleratorTestEnvironment() {}
void VideoDecodeAcceleratorTestEnvironment::SetUp() {
base::Thread::Options options;
options.message_pump_type = base::MessagePumpType::UI;
rendering_thread_.StartWithOptions(options);
base::WaitableEvent done(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
rendering_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&RenderingHelper::InitializeOneOff,
use_gl_renderer_, &done));
done.Wait();
#if defined(OS_CHROMEOS)
gpu_helper_.reset(new ui::OzoneGpuTestHelper());
// Need to initialize after the rendering side since the rendering side
// initializes the "GPU" parts of Ozone.
//
// This also needs to be done in the test environment since this shouldn't
// be initialized multiple times for the same Ozone platform.
gpu_helper_->Initialize(base::ThreadTaskRunnerHandle::Get());
#endif
}
void VideoDecodeAcceleratorTestEnvironment::TearDown() {
#if defined(OS_CHROMEOS)
gpu_helper_.reset();
#endif
rendering_thread_.Stop();
}
scoped_refptr<base::SingleThreadTaskRunner>
VideoDecodeAcceleratorTestEnvironment::GetRenderingTaskRunner() const {
return rendering_thread_.task_runner();
}
EncodedDataHelper::EncodedDataHelper(const std::string& data,
VideoCodecProfile profile)
: data_(data), profile_(profile) {}
EncodedDataHelper::EncodedDataHelper(const std::vector<uint8_t>& stream,
VideoCodecProfile profile)
: EncodedDataHelper(
std::string(reinterpret_cast<const char*>(stream.data()),
stream.size()),
profile) {}
EncodedDataHelper::~EncodedDataHelper() {
base::STLClearObject(&data_);
}
bool EncodedDataHelper::IsNALHeader(const std::string& data, size_t pos) {
return data[pos] == 0 && data[pos + 1] == 0 && data[pos + 2] == 0 &&
data[pos + 3] == 1;
}
std::string EncodedDataHelper::GetBytesForNextData() {
switch (VideoCodecProfileToVideoCodec(profile_)) {
case kCodecH264:
return GetBytesForNextFragment();
case kCodecVP8:
case kCodecVP9:
return GetBytesForNextFrame();
default:
NOTREACHED();
return std::string();
}
}
std::string EncodedDataHelper::GetBytesForNextFragment() {
if (next_pos_to_decode_ == 0) {
size_t skipped_fragments_count = 0;
if (!LookForSPS(&skipped_fragments_count)) {
next_pos_to_decode_ = 0;
return std::string();
}
num_skipped_fragments_ += skipped_fragments_count;
}
size_t start_pos = next_pos_to_decode_;
size_t next_nalu_pos = GetBytesForNextNALU(start_pos);
// Update next_pos_to_decode_.
next_pos_to_decode_ = next_nalu_pos;
return data_.substr(start_pos, next_nalu_pos - start_pos);
}
size_t EncodedDataHelper::GetBytesForNextNALU(size_t start_pos) {
size_t pos = start_pos;
if (pos + 4 > data_.size())
return pos;
LOG_ASSERT(IsNALHeader(data_, pos));
pos += 4;
while (pos + 4 <= data_.size() && !IsNALHeader(data_, pos)) {
++pos;
}
if (pos + 3 >= data_.size())
pos = data_.size();
return pos;
}
bool EncodedDataHelper::LookForSPS(size_t* skipped_fragments_count) {
*skipped_fragments_count = 0;
while (next_pos_to_decode_ + 4 < data_.size()) {
if ((data_[next_pos_to_decode_ + 4] & 0x1f) == 0x7) {
return true;
}
*skipped_fragments_count += 1;
next_pos_to_decode_ = GetBytesForNextNALU(next_pos_to_decode_);
}
return false;
}
std::string EncodedDataHelper::GetBytesForNextFrame() {
// Helpful description: http://wiki.multimedia.cx/index.php?title=IVF
size_t pos = next_pos_to_decode_;
std::string bytes;
if (pos == 0)
pos = 32; // Skip IVF header.
uint32_t frame_size = *reinterpret_cast<uint32_t*>(&data_[pos]);
pos += 12; // Skip frame header.
bytes.append(data_.substr(pos, frame_size));
// Update next_pos_to_decode_.
next_pos_to_decode_ = pos + frame_size;
return bytes;
}
// static
bool EncodedDataHelper::HasConfigInfo(const uint8_t* data,
size_t size,
VideoCodecProfile profile) {
if (profile >= H264PROFILE_MIN && profile <= H264PROFILE_MAX) {
H264Parser parser;
parser.SetStream(data, size);
H264NALU nalu;
H264Parser::Result result = parser.AdvanceToNextNALU(&nalu);
if (result != H264Parser::kOk) {
// Let the VDA figure out there's something wrong with the stream.
return false;
}
return nalu.nal_unit_type == H264NALU::kSPS;
} else if (profile >= VP8PROFILE_MIN && profile <= VP9PROFILE_MAX) {
return (size > 0 && !(data[0] & 0x01));
}
// Shouldn't happen at this point.
LOG(FATAL) << "Invalid profile: " << GetProfileName(profile);
return false;
}
// Read in golden MD5s for the thumbnailed rendering of this video
std::vector<std::string> ReadGoldenThumbnailMD5s(
const base::FilePath& md5_file_path) {
std::vector<std::string> golden_md5s;
std::vector<std::string> md5_strings;
std::string all_md5s;
base::ReadFileToString(md5_file_path, &all_md5s);
md5_strings = base::SplitString(all_md5s, "\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
// Check these are legitimate MD5s.
for (const std::string& md5_string : md5_strings) {
// Ignore the empty string added by SplitString
if (!md5_string.length())
continue;
// Ignore comments
if (md5_string.at(0) == '#')
continue;
bool valid_length = md5_string.length() == kMD5StringLength;
LOG_IF(ERROR, !valid_length) << "MD5 length error: " << md5_string;
bool hex_only = std::count_if(md5_string.begin(), md5_string.end(),
isxdigit) == kMD5StringLength;
LOG_IF(ERROR, !hex_only) << "MD5 includes non-hex char: " << md5_string;
if (valid_length && hex_only)
golden_md5s.push_back(md5_string);
}
LOG_IF(ERROR, md5_strings.empty())
<< " MD5 checksum file (" << md5_file_path.MaybeAsASCII()
<< ") missing or empty.";
return golden_md5s;
}
bool ConvertRGBAToRGB(const std::vector<unsigned char>& rgba,
std::vector<unsigned char>* rgb) {
size_t num_pixels = rgba.size() / 4;
rgb->resize(num_pixels * 3);
// Drop the alpha channel, but check as we go that it is all 0xff.
bool solid = true;
for (size_t i = 0; i < num_pixels; i++) {
(*rgb)[3 * i] = rgba[4 * i];
(*rgb)[3 * i + 1] = rgba[4 * i + 1];
(*rgb)[3 * i + 2] = rgba[4 * i + 2];
solid = solid && (rgba[4 * i + 3] == 0xff);
}
return solid;
}
} // namespace test
} // namespace media