blob: 02b6e2aab72e6f164eebb3cf1e8ae1d9ae10c0ec [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 <byteswap.h>
#include <zircon/compiler.h>
#include <openssl/sha.h>
#include "amlogic-video.h"
#include "gtest/gtest.h"
#include "hevcdec.h"
#include "macros.h"
#include "pts_manager.h"
#include "test_25fps_vp9_hashes.h"
#include "test_frame_allocator.h"
#include "tests/test_support.h"
#include "video_frame_helpers.h"
#include "vp9_decoder.h"
#include "vp9_utils.h"
struct __attribute__((__packed__)) IvfHeader {
uint32_t signature;
uint16_t version;
uint16_t header_length;
uint32_t fourcc;
uint16_t width;
uint16_t height;
uint32_t frame_rate;
uint32_t time_scale;
uint32_t frame_count;
uint32_t unused;
};
struct __attribute__((__packed__)) IvfFrameHeader {
uint32_t size_bytes;
uint64_t presentation_timestamp;
};
struct FrameData {
uint64_t presentation_timestamp;
std::vector<uint8_t> data;
};
std::vector<uint8_t> ConvertIvfToAmlV(const uint8_t* data, uint32_t length) {
uint32_t offset = sizeof(IvfHeader);
std::vector<uint8_t> output_vector;
while (offset < length) {
auto frame_header = reinterpret_cast<const IvfFrameHeader*>(data + offset);
uint32_t frame_size = frame_header->size_bytes;
uint32_t data_offset = offset + sizeof(IvfFrameHeader);
if (data_offset + frame_size > length) {
DECODE_ERROR("Invalid IVF file, truncating\n");
return output_vector;
}
SplitSuperframe(data + data_offset, frame_size, &output_vector);
offset = data_offset + frame_size;
}
return output_vector;
}
// Split IVF-level frames
std::vector<FrameData> ConvertIvfToAmlVFrames(const uint8_t* data, uint32_t length) {
uint32_t offset = sizeof(IvfHeader);
std::vector<FrameData> output_vector;
while (offset < length) {
auto frame_header = reinterpret_cast<const IvfFrameHeader*>(data + offset);
uint32_t frame_size = frame_header->size_bytes;
uint32_t data_offset = offset + sizeof(IvfFrameHeader);
if (data_offset + frame_size > length) {
DECODE_ERROR("Invalid IVF file, truncating\n");
return output_vector;
}
FrameData frame_data;
frame_data.presentation_timestamp = frame_header->presentation_timestamp;
SplitSuperframe(data + data_offset, frame_size, &frame_data.data);
output_vector.push_back(std::move(frame_data));
offset = data_offset + frame_size;
}
return output_vector;
}
class TestFrameProvider final : public Vp9Decoder::FrameDataProvider {
public:
// Always claim that 50 more bytes are available. Due to the 16kB of padding
// at the end this is always true.
void ReadMoreInputData(Vp9Decoder* decoder) override { decoder->UpdateDecodeSize(50); }
void ReadMoreInputDataFromReschedule(Vp9Decoder* decoder) override { ReadMoreInputData(decoder); }
bool HasMoreInputData() override {
// If the input context hasn't been created yet then no data has been
// decoded, so more must exist.
return (!instance_->input_context() || (instance_->input_context()->processed_video <
instance_->stream_buffer()->data_size()));
}
void set_instance(DecoderInstance* instance) { instance_ = instance; }
private:
DecoderInstance* instance_ = nullptr;
};
class Vp9TestClient : public TestFrameAllocator {
public:
explicit Vp9TestClient(AmlogicVideo* video) : TestFrameAllocator(video) {}
bool IsOutputReady() override { return true; }
bool IsCurrentOutputBufferCollectionUsable(uint32_t min_frame_count, uint32_t max_frame_count,
uint32_t coded_width, uint32_t coded_height,
uint32_t stride, uint32_t display_width,
uint32_t display_height) override {
// Assume that these tests never resize outputs.
return true;
}
};
// Repeatedly try to process video, either it's all processed or until a flag is set.
static void FeedDataUntilFlag(AmlogicVideo* video, const uint8_t* input, uint32_t input_size,
std::atomic<bool>* stop_parsing) {
uint32_t current_offset = 0;
while (!*stop_parsing) {
uint32_t processed_data;
EXPECT_EQ(ZX_OK, video->ProcessVideoNoParser(input + current_offset,
input_size - current_offset, &processed_data));
current_offset += processed_data;
if (current_offset == input_size)
break;
zx_nanosleep(zx_deadline_after(ZX_MSEC(15)));
}
}
static const uint8_t kFlushThroughBytes[16384] = {};
constexpr uint32_t kTestVideoFrameCount = 249;
class TestVP9 {
public:
static void Decode(bool use_parser, bool use_compressed_output, bool delayed_return,
const char* input_filename, const char* filename, bool test_hashes) {
auto video = std::make_unique<AmlogicVideo>();
ASSERT_TRUE(video);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
Vp9TestClient client(video.get());
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->SetDefaultInstance(
std::make_unique<Vp9Decoder>(video.get(), &client, Vp9Decoder::InputType::kSingleStream,
use_compressed_output, false),
true);
}
EXPECT_EQ(ZX_OK, video->InitializeStreamBuffer(use_parser, PAGE_SIZE, /*is_secure=*/false));
if (use_parser) {
EXPECT_EQ(ZX_OK, video->InitializeEsParser());
}
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
client.set_decoder(video->video_decoder_);
EXPECT_EQ(ZX_OK, video->video_decoder_->Initialize());
}
uint32_t frame_count = 0;
std::promise<void> wait_valid;
bool frames_returned = false; // Protected by video->video_decoder_lock_
std::vector<std::weak_ptr<VideoFrame>> frames_to_return;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
client.SetFrameReadyNotifier([&video, &frames_to_return, &frame_count, &wait_valid,
&frames_returned, delayed_return, filename,
test_hashes](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d\n", frame_count);
EXPECT_EQ(320u, frame->display_width);
EXPECT_EQ(240u, frame->display_height);
(void)filename;
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame.get(), filename);
#endif
if (test_hashes) {
uint8_t md[SHA256_DIGEST_LENGTH];
HashFrame(frame.get(), md);
EXPECT_EQ(0, memcmp(md, test_25fps_hashes[frame_count - 1], sizeof(md)))
<< "Incorrect hash for frame " << frame_count << ": " << StringifyHash(md);
}
if (frames_returned || !delayed_return)
ReturnFrame(video.get(), frame);
else
frames_to_return.push_back(frame);
if (frame_count == kTestVideoFrameCount)
wait_valid.set_value();
// Testing delayed return doesn't work well with reallocating buffers, since the
// decoder will throw out the old buffers and continue decoding anyway.
if (!delayed_return && (frame_count % 5 == 0))
SetReallocateBuffersNextFrameForTesting(video.get());
});
}
auto test_ivf = TestSupport::LoadFirmwareFile(input_filename);
ASSERT_NE(nullptr, test_ivf);
std::atomic<bool> stop_parsing(false);
// Put on a separate thread because it needs video decoding to progress in
// order to finish.
auto parser = std::async([&video, use_parser, &test_ivf, &stop_parsing]() {
auto aml_data = ConvertIvfToAmlV(test_ivf->ptr, test_ivf->size);
if (use_parser) {
EXPECT_EQ(ZX_OK, video->parser()->ParseVideo(aml_data.data(), aml_data.size()));
EXPECT_EQ(ZX_OK, video->parser()->WaitForParsingCompleted(ZX_SEC(10)));
EXPECT_EQ(ZX_OK,
video->parser()->ParseVideo(kFlushThroughBytes, sizeof(kFlushThroughBytes)));
EXPECT_EQ(ZX_OK, video->parser()->WaitForParsingCompleted(ZX_SEC(10)));
} else {
video->core_->InitializeDirectInput();
FeedDataUntilFlag(video.get(), aml_data.data(), aml_data.size(), &stop_parsing);
FeedDataUntilFlag(video.get(), kFlushThroughBytes, sizeof(kFlushThroughBytes),
&stop_parsing);
}
});
zx_nanosleep(zx_deadline_after(ZX_SEC(1)));
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
for (auto& frame : frames_to_return) {
std::shared_ptr<VideoFrame> locked_ptr(frame.lock());
if (locked_ptr) {
video->video_decoder_->ReturnFrame(std::move(locked_ptr));
}
}
frames_returned = true;
}
EXPECT_EQ(std::future_status::ready,
wait_valid.get_future().wait_for(std::chrono::seconds(10)));
stop_parsing = true;
EXPECT_EQ(std::future_status::ready, parser.wait_for(std::chrono::seconds(1)));
video.reset();
}
static void DecodePerFrame() {
auto video = std::make_unique<AmlogicVideo>();
ASSERT_TRUE(video);
Vp9TestClient client(video.get());
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
auto test_ivf = TestSupport::LoadFirmwareFile("video_test_data/test-25fps.vp9");
ASSERT_NE(nullptr, test_ivf);
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->SetDefaultInstance(
std::make_unique<Vp9Decoder>(video.get(), &client, Vp9Decoder::InputType::kSingleStream,
false, false),
true);
}
EXPECT_EQ(ZX_OK, video->InitializeStreamBuffer(/*use_parser=*/true, PAGE_SIZE,
/*is_secure=*/false));
EXPECT_EQ(ZX_OK, video->InitializeEsParser());
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
client.set_decoder(video->video_decoder_);
EXPECT_EQ(ZX_OK, video->video_decoder_->Initialize());
}
uint32_t frame_count = 0;
std::promise<void> wait_valid;
std::vector<std::shared_ptr<VideoFrame>> frames_to_return;
uint64_t next_pts = 0;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
client.SetFrameReadyNotifier(
[&video, &frame_count, &wait_valid, &next_pts](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d, pts: %ld\n", frame_count, frame->pts);
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame, filename);
#endif
EXPECT_TRUE(frame->has_pts);
// All frames are shown, so pts should be in order. Due to rounding,
// pts may be 1 off.
EXPECT_LE(next_pts, frame->pts);
EXPECT_GE(next_pts + 1, frame->pts);
// 25 fps video
next_pts = frame->pts + 1000 / 25;
ReturnFrame(video.get(), frame);
if (frame_count == kTestVideoFrameCount)
wait_valid.set_value();
});
}
// Put on a separate thread because it needs video decoding to progress in
// order to finish.
auto parser = std::async([&video, &test_ivf]() {
auto aml_data = ConvertIvfToAmlVFrames(test_ivf->ptr, test_ivf->size);
uint32_t stream_offset = 0;
for (auto& data : aml_data) {
video->pts_manager()->InsertPts(stream_offset, true, data.presentation_timestamp);
EXPECT_EQ(ZX_OK, video->parser()->ParseVideo(data.data.data(), data.data.size()));
EXPECT_EQ(ZX_OK, video->parser()->WaitForParsingCompleted(ZX_SEC(10)));
stream_offset += data.data.size();
}
EXPECT_EQ(ZX_OK, video->parser()->ParseVideo(kFlushThroughBytes, sizeof(kFlushThroughBytes)));
EXPECT_EQ(ZX_OK, video->parser()->WaitForParsingCompleted(ZX_SEC(10)));
});
EXPECT_EQ(std::future_status::ready, wait_valid.get_future().wait_for(std::chrono::seconds(2)));
EXPECT_EQ(std::future_status::ready, parser.wait_for(std::chrono::seconds(1)));
video.reset();
}
static void DecodeResetHardware(const char* filename, bool use_parser) {
auto video = std::make_unique<AmlogicVideo>();
ASSERT_TRUE(video);
Vp9TestClient client(video.get());
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->SetDefaultInstance(
std::make_unique<Vp9Decoder>(video.get(), &client, Vp9Decoder::InputType::kMultiStream,
false, false),
true);
}
// Don't use parser, because we need to be able to save and restore the read
// and write pointers, which can't be done if the parser is using them as
// well.
EXPECT_EQ(ZX_OK, video->InitializeStreamBuffer(/*use_parser=*/false, 1024 * PAGE_SIZE,
/*is_secure=*/false));
TestFrameProvider frame_provider;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
client.set_decoder(video->video_decoder_);
static_cast<Vp9Decoder*>(video->video_decoder_)->SetFrameDataProvider(&frame_provider);
frame_provider.set_instance(video->current_instance());
EXPECT_EQ(ZX_OK, video->video_decoder_->Initialize());
}
uint32_t frame_count = 0;
std::promise<void> wait_valid;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
client.SetFrameReadyNotifier(
[&video, &frame_count, &wait_valid](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d\n", frame_count);
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame.get(), filename);
#endif
ReturnFrame(video.get(), frame);
if (frame_count == 50)
wait_valid.set_value();
});
}
auto test_ivf = TestSupport::LoadFirmwareFile("video_test_data/test-25fps.vp9");
ASSERT_NE(nullptr, test_ivf);
auto aml_data = ConvertIvfToAmlVFrames(test_ivf->ptr, test_ivf->size);
video->core_->InitializeDirectInput();
const uint8_t kPadding[16384] = {};
if (use_parser) {
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
EXPECT_EQ(ZX_OK, video->parser()->InitializeEsParser(nullptr));
video->parser()->SyncFromDecoderInstance(video->current_instance());
for (uint32_t i = 0; i < 50; i++) {
EXPECT_EQ(ZX_OK,
video->parser()->ParseVideo(aml_data[i].data.data(), aml_data[i].data.size()));
EXPECT_EQ(ZX_OK, video->parser()->WaitForParsingCompleted(ZX_SEC(1)));
}
// Force all frames to be processed.
EXPECT_EQ(ZX_OK, video->parser()->ParseVideo(kPadding, sizeof(kPadding)));
EXPECT_EQ(ZX_OK, video->parser()->WaitForParsingCompleted(ZX_SEC(1)));
video->parser()->SyncToDecoderInstance(video->current_instance());
} else {
// Only use the first 50 frames to save time.
for (uint32_t i = 0; i < 50; i++) {
EXPECT_EQ(ZX_OK,
video->ProcessVideoNoParser(aml_data[i].data.data(), aml_data[i].data.size()));
}
// Force all frames to be processed.
EXPECT_EQ(ZX_OK, video->ProcessVideoNoParser(kPadding, sizeof(kPadding)));
}
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
static_cast<Vp9Decoder*>(video->video_decoder())->UpdateDecodeSize(50);
}
EXPECT_EQ(std::future_status::ready, wait_valid.get_future().wait_for(std::chrono::seconds(2)));
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->current_instance_.reset();
video->video_decoder_ = nullptr;
}
video.reset();
}
static void DecodeMultiInstance(bool inject_initialization_fault) {
auto video = std::make_unique<AmlogicVideo>();
ASSERT_TRUE(video);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
std::vector<std::unique_ptr<TestFrameProvider>> frame_providers;
std::vector<std::unique_ptr<TestFrameAllocator>> clients;
for (uint32_t i = 0; i < 2; i++) {
auto client = std::make_unique<Vp9TestClient>(video.get());
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
auto decoder = std::make_unique<Vp9Decoder>(
video.get(), client.get(), Vp9Decoder::InputType::kMultiStream, false, false);
frame_providers.push_back(std::make_unique<TestFrameProvider>());
decoder->SetFrameDataProvider(frame_providers.back().get());
client->set_decoder(decoder.get());
clients.push_back(std::move(client));
EXPECT_EQ(ZX_OK, decoder->InitializeBuffers());
video->swapped_out_instances_.push_back(
std::make_unique<DecoderInstance>(std::move(decoder), video->hevc_core_.get()));
StreamBuffer* buffer = video->swapped_out_instances_.back()->stream_buffer();
EXPECT_EQ(ZX_OK, video->AllocateStreamBuffer(buffer, PAGE_SIZE * 1024, /*use_parser=*/false,
/*is_secure=*/false));
frame_providers.back()->set_instance(video->swapped_out_instances_.back().get());
}
{
// TODO: Use production code to schedule in the first instance.
// AmlogicVideo::TryToSchedule() currently tries to read data and start
// decoding, which is not quite what we want here.
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->current_instance_ = std::move(video->swapped_out_instances_.front());
video->swapped_out_instances_.pop_front();
video->video_decoder_ = video->current_instance_->decoder();
video->stream_buffer_ = video->current_instance_->stream_buffer();
video->core_ = video->current_instance_->core();
video->core_->PowerOn();
EXPECT_EQ(ZX_OK, static_cast<Vp9Decoder*>(video->video_decoder_)->InitializeHardware());
}
// Don't use parser, because we need to be able to save and restore the read
// and write pointers, which can't be done if the parser is using them as
// well.
video->InitializeStreamInput(false);
uint32_t frame_count = 0;
std::promise<void> wait_valid;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
clients[0]->SetFrameReadyNotifier(
[&video, &frame_count, &wait_valid](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d\n", frame_count);
DLOG("coded_width: %d, coded_height: %d\n", frame->coded_width, frame->coded_height);
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame.get(), "/tmp/bearmulti1.yuv");
#endif
ReturnFrame(video.get(), frame);
if (frame_count == 50)
wait_valid.set_value();
});
}
uint32_t frame_count1 = 0;
std::promise<void> wait_valid1;
bool got_error = false;
{
clients[1]->SetFrameReadyNotifier(
[&video, &frame_count1, &wait_valid1,
inject_initialization_fault](std::shared_ptr<VideoFrame> frame) {
// This is called from the interrupt handler, which already holds the lock.
video->AssertVideoDecoderLockHeld();
++frame_count1;
DLOG("Decoder 2 Got frame %d\n", frame_count1);
EXPECT_EQ(320u, frame->display_width);
EXPECT_EQ(240u, frame->display_height);
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame.get(), "/tmp/bearmulti2.yuv");
#endif
ReturnFrame(video.get(), frame);
constexpr uint32_t kFrameToFaultAt = 20;
if (frame_count1 == kFrameToFaultAt && inject_initialization_fault) {
static_cast<Vp9Decoder*>(video->video_decoder())->InjectInitializationFault();
}
if (inject_initialization_fault) {
// If an initialization fault was injected, decoding shouldn't continue.
EXPECT_LE(frame_count1, kFrameToFaultAt);
} else {
if (frame_count1 == 30)
wait_valid1.set_value();
}
});
clients[1]->SetErrorHandler([&got_error, &wait_valid1]() {
got_error = true;
wait_valid1.set_value();
});
}
// The default stack size is ZIRCON_DEFAULT_STACK_SIZE - 256kB.
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
StreamBuffer* buffer = video->current_instance_->stream_buffer();
auto test_ivf = TestSupport::LoadFirmwareFile("video_test_data/test-25fps.vp9");
ASSERT_NE(nullptr, test_ivf);
auto aml_data = ConvertIvfToAmlVFrames(test_ivf->ptr, test_ivf->size);
video->core_->InitializeDirectInput();
// Only use the first 50 frames to save time.
for (uint32_t i = 0; i < 50; i++) {
EXPECT_EQ(ZX_OK,
video->ProcessVideoNoParser(aml_data[i].data.data(), aml_data[i].data.size()));
}
buffer->set_padding_size(sizeof(kFlushThroughBytes));
// Force all frames to be processed.
EXPECT_EQ(ZX_OK, video->ProcessVideoNoParser(kFlushThroughBytes, sizeof(kFlushThroughBytes)));
}
// Normally we'd probably want to always fill the stream buffer when the
// decoder is attached to the hardware, but for testing we should try
// filling the buffer when it's not attached, to ensure we can correctly
// initialize the write pointer later.
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
auto test_ivf2 = TestSupport::LoadFirmwareFile("video_test_data/test-25fps.vp9_2");
ASSERT_NE(nullptr, test_ivf2);
auto aml_data2 = ConvertIvfToAmlVFrames(test_ivf2->ptr, test_ivf2->size);
StreamBuffer* buffer = video->swapped_out_instances_.back()->stream_buffer();
uint32_t offset = 0;
// Only use the first 30 frames to save time. Ensure this is different
// from above, to test whether ending decoding early works.
for (uint32_t i = 0; i < 30; i++) {
memcpy(buffer->buffer().virt_base() + offset, aml_data2[i].data.data(),
aml_data2[i].data.size());
offset += aml_data2[i].data.size();
}
buffer->set_data_size(offset);
buffer->set_padding_size(sizeof(kFlushThroughBytes));
memcpy(buffer->buffer().virt_base() + offset, kFlushThroughBytes, sizeof(kFlushThroughBytes));
offset += sizeof(kFlushThroughBytes);
buffer->buffer().CacheFlush(0, offset);
}
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
static_cast<Vp9Decoder*>(video->video_decoder())->UpdateDecodeSize(50);
}
EXPECT_EQ(std::future_status::ready,
wait_valid.get_future().wait_for(std::chrono::seconds(10)));
EXPECT_EQ(std::future_status::ready,
wait_valid1.get_future().wait_for(std::chrono::seconds(10)));
EXPECT_EQ(50u, frame_count);
if (inject_initialization_fault) {
EXPECT_TRUE(got_error);
EXPECT_EQ(20u, frame_count1);
} else {
EXPECT_FALSE(got_error);
EXPECT_EQ(30u, frame_count1);
}
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->current_instance_.reset();
video->swapped_out_instances_.clear();
video->video_decoder_ = nullptr;
}
video.reset();
}
static void DecodeMalformed(const char* input_filename,
const std::vector<std::pair<uint32_t, uint8_t>>& modifications) {
auto video = std::make_unique<AmlogicVideo>();
ASSERT_TRUE(video);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
Vp9TestClient client(video.get());
std::promise<void> first_wait_valid;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->SetDefaultInstance(
std::make_unique<Vp9Decoder>(video.get(), &client, Vp9Decoder::InputType::kSingleStream,
/*use_compressed_output=*/false, false),
true);
client.SetErrorHandler([&first_wait_valid]() {
DLOG("Got decode error");
first_wait_valid.set_value();
});
}
EXPECT_EQ(ZX_OK,
video->InitializeStreamBuffer(/*use_parser=*/true, PAGE_SIZE, /*is_secure=*/false));
EXPECT_EQ(ZX_OK, video->InitializeEsParser());
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
client.set_decoder(video->video_decoder_);
EXPECT_EQ(ZX_OK, video->video_decoder_->Initialize());
}
uint32_t frame_count = 0;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
client.SetFrameReadyNotifier([&video, &frame_count](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DECODE_ERROR("Got frame %d", frame_count);
DLOG("Got frame %d\n", frame_count);
EXPECT_EQ(320u, frame->display_width);
EXPECT_EQ(240u, frame->display_height);
ReturnFrame(video.get(), frame);
});
}
auto test_ivf = TestSupport::LoadFirmwareFile(input_filename);
ASSERT_NE(nullptr, test_ivf);
auto aml_data = ConvertIvfToAmlV(test_ivf->ptr, test_ivf->size);
// Arbitrary modifications to an AMLV header shouldn't happen in production code,
// because the driver is what creates that. The rest is fair game, though.
for (auto& mod : modifications) {
aml_data[mod.first] = mod.second;
}
EXPECT_EQ(ZX_OK, video->parser()->ParseVideo(aml_data.data(), aml_data.size()));
EXPECT_EQ(std::future_status::ready,
first_wait_valid.get_future().wait_for(std::chrono::seconds(1)));
// The decoder should now be hung without having gotten through all the input so we should
// cancel parsing before teardown.
video->parser()->CancelParsing();
video.reset();
}
private:
// This is called from the interrupt handler, which already holds the lock.
static void ReturnFrame(AmlogicVideo* video, std::shared_ptr<VideoFrame> frame) {
video->AssertVideoDecoderLockHeld();
video->video_decoder_->ReturnFrame(frame);
}
static void SetReallocateBuffersNextFrameForTesting(AmlogicVideo* video) {
video->AssertVideoDecoderLockHeld();
static_cast<Vp9Decoder*>(video->video_decoder_)
->set_reallocate_buffers_next_frame_for_testing();
}
};
class VP9Compression : public ::testing::TestWithParam</*compressed_output=*/bool> {};
TEST_P(VP9Compression, Decode) {
TestVP9::Decode(true, GetParam(), false, "video_test_data/test-25fps.vp9", "/tmp/bearvp9.yuv",
true);
}
TEST_P(VP9Compression, DecodeDelayedReturn) {
TestVP9::Decode(true, GetParam(), true, "video_test_data/test-25fps.vp9", "/tmp/bearvp9.yuv",
true);
}
TEST_P(VP9Compression, DecodeNoParser) {
TestVP9::Decode(false, GetParam(), false, "video_test_data/test-25fps.vp9",
"/tmp/bearvp9noparser.yuv", true);
}
TEST_P(VP9Compression, Decode10Bit) {
TestVP9::Decode(false, GetParam(), false, "video_test_data/test-25fps.vp9_2",
"/tmp/bearvp9noparser.yuv", false);
}
INSTANTIATE_TEST_SUITE_P(VP9CompressionOptional, VP9Compression, ::testing::Bool());
TEST(VP9, DecodePerFrame) { TestVP9::DecodePerFrame(); }
TEST(VP9, DecodeResetHardware) { TestVP9::DecodeResetHardware("/tmp/bearvp9reset.yuv", false); }
TEST(VP9, DecodeResetHardwareWithParser) {
TestVP9::DecodeResetHardware("/tmp/bearvp9resetwithparser.yuv", true);
}
TEST(VP9, DecodeMultiInstance) { TestVP9::DecodeMultiInstance(false); }
TEST(VP9, DecodeMultiInstanceWithInitializationFault) { TestVP9::DecodeMultiInstance(true); }
TEST(VP9, DecodeMalformedHang) {
// Numbers are essentially random, but picked to ensure the decoder would
// normally hang. The offset should be >= 16 to avoid hitting the AMLV header.
TestVP9::DecodeMalformed("video_test_data/test-25fps.vp9", {{17, 10}});
}
TEST(VP9, DecodeMalformedLarge) {
constexpr uint32_t kAmlVHeaderSize = 16;
// Modify bits [12, 15] of the width to 0xf (and keep colorspace the same at 0x0).
// The width should now be 0xf13f.
constexpr uint32_t kWidthModificationOffset = kAmlVHeaderSize + 4;
// Modify bits [12, 15] of the height to 0xf (and keep the width the same, since its low 4 bits
// are already 0xf). The height should now be 0xf0ef.
constexpr uint32_t kHeightModificationOffset = kAmlVHeaderSize + 6;
TestVP9::DecodeMalformed("video_test_data/test-25fps.vp9",
{{kWidthModificationOffset, 0x0f}, {kHeightModificationOffset, 0xff}});
}