blob: 1653ed6de11f46661f315b31f0e84f39cd95b779 [file] [log] [blame]
// Copyright 2020 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 <gtest/gtest.h>
#include "amlogic-video.h"
#include "bear_h264_hashes.h"
#include "h264_multi_decoder.h"
#include "h264_utils.h"
#include "lib/async-loop/default.h"
#include "macros.h"
#include "pts_manager.h"
#include "test_frame_allocator.h"
#include "tests/integration/test_25fps_h264_hashes.h"
#include "tests/test_support.h"
#include "vdec1.h"
#include "video_frame_helpers.h"
namespace amlogic_decoder {
namespace test {
class H264TestFrameDataProvider final : public H264MultiDecoder::FrameDataProvider {
public:
H264TestFrameDataProvider(AmlogicVideo* video)
: video_(video), async_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {
ZX_ASSERT(ZX_OK == async_loop_.StartThread("async_loop_"));
}
void set_decoder(H264MultiDecoder* decoder) { decoder_ = decoder; }
void AppendFrameData(std::vector<std::vector<uint8_t>> frame_data) {
frame_data_.insert(frame_data_.end(), frame_data.begin(), frame_data.end());
}
void set_async_reset_handler(fit::closure handler) { async_reset_handler_ = std::move(handler); }
std::optional<H264MultiDecoder::DataInput> ReadMoreInputData() override {
H264MultiDecoder::DataInput result;
if (frame_data_.empty()) {
return std::nullopt;
}
result.data = std::move(frame_data_.front());
result.length = result.data.size();
frame_data_.pop_front();
uint32_t nal_unit_type = GetNalUnitType(result.data);
if (nal_unit_type == 1 || nal_unit_type == 5) {
// Only assign PTS for slices, to try to avoid jumps.
result.pts = next_pts_++;
}
return result;
}
bool HasMoreInputData() override { return !frame_data_.empty(); }
void AsyncPumpDecoder() override {
async::PostTask(async_loop_.dispatcher(), [this] {
std::lock_guard<std::mutex> lock(*video_->video_decoder_lock());
decoder_->PumpOrReschedule();
});
}
void AsyncResetStreamAfterCurrentFrame() override {
EXPECT_TRUE(async_reset_handler_);
async_reset_handler_();
}
private:
AmlogicVideo* video_ = nullptr;
H264MultiDecoder* decoder_ = nullptr;
std::list<std::vector<uint8_t>> frame_data_;
uint64_t next_pts_{};
fit::closure async_reset_handler_;
async::Loop async_loop_;
};
class FakeOwner : public AmlogicVideo::Owner {
public:
// AmlogicVideo::Owner implementation.
void SetThreadProfile(zx::unowned_thread thread, ThreadRole role) const override {}
};
class TestH264Multi {
public:
struct VideoInfo {
const char* input_filename;
uint8_t (*input_hashes)[SHA256_DIGEST_LENGTH];
const char* filename;
uint32_t coded_width;
uint32_t coded_height;
uint32_t display_width;
uint32_t display_height;
uint32_t expected_frame_count;
bool has_sar;
};
static void DecodeSetStream(const VideoInfo& data, bool use_parser) {
FakeOwner owner;
auto video = std::make_unique<AmlogicVideo>(&owner);
ASSERT_TRUE(video);
TestFrameAllocator frame_allocator(video.get());
video->SetDeviceType(DeviceType::kSM1);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
H264TestFrameDataProvider frame_data_provider(video.get());
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
auto decoder = std::make_unique<H264MultiDecoder>(
video.get(), &frame_allocator, &frame_data_provider, std::nullopt, /*is_secure=*/false);
frame_data_provider.set_decoder(decoder.get());
decoder->set_use_parser(use_parser);
video->SetDefaultInstance(std::move(decoder),
/*hevc=*/false);
frame_allocator.set_decoder(video->video_decoder());
}
frame_allocator.set_pump_function([&video]() {
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
static_cast<H264MultiDecoder*>(video->video_decoder())->PumpOrReschedule();
});
// 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, 1024 * PAGE_SIZE,
/*is_secure=*/false));
uint32_t frame_count = 0;
std::promise<void> wait_valid;
std::set<uint64_t> received_pts_set;
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_allocator.SetFrameReadyNotifier([&video, &frame_count, &wait_valid, &data,
&received_pts_set,
&frame_allocator](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d\n", frame_count);
EXPECT_EQ(data.coded_width, frame->coded_width);
EXPECT_EQ(data.display_width, frame->display_width);
EXPECT_EQ(data.coded_height, frame->coded_height);
EXPECT_EQ(data.display_height, frame->display_height);
bool is_bear = data.input_filename == std::string("video_test_data/bear.h264");
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame.get(), data.filename);
#endif
io_buffer_cache_flush_invalidate(&frame->buffer, 0, frame->stride * frame->coded_height);
io_buffer_cache_flush_invalidate(&frame->buffer, frame->uv_plane_offset,
frame->stride * frame->coded_height / 2);
uint8_t* buf_start = static_cast<uint8_t*>(io_buffer_virt(&frame->buffer));
if (frame_count == 1 && is_bear) {
// Only test a small amount to try to make the output of huge failures obvious - the rest
// can be verified through hashes.
constexpr uint8_t kExpectedData[] = {124, 186, 230, 247, 252, 252, 252, 252, 252, 252};
for (uint32_t i = 0; i < std::size(kExpectedData); ++i) {
EXPECT_EQ(kExpectedData[i], buf_start[i]) << " index " << i;
}
}
if (data.input_hashes) {
uint8_t md[SHA256_DIGEST_LENGTH];
HashFrame(frame.get(), md);
EXPECT_EQ(0, memcmp(md, data.input_hashes[frame_count - 1], sizeof(md)))
<< "Incorrect hash for frame " << frame_count << ": " << StringifyHash(md);
}
EXPECT_TRUE(frame->has_pts);
// The "pts" assigned in the TestFrameDataProvider goes in decode order, so we need to allow
// the current one to be two less than the max previously seen.
if (received_pts_set.size() > 0)
EXPECT_LE(*std::prev(received_pts_set.end()), frame->pts + 2);
EXPECT_EQ(0u, received_pts_set.count(frame->pts));
received_pts_set.insert(frame->pts);
EXPECT_EQ(data.has_sar, frame_allocator.has_sar());
video->AssertVideoDecoderLockHeld();
video->video_decoder()->ReturnFrame(frame);
if (frame_count == data.expected_frame_count) {
wait_valid.set_value();
}
});
// Initialize must happen after InitializeStreamBuffer or else it may misparse the SPS.
EXPECT_EQ(ZX_OK, video->video_decoder()->Initialize());
}
auto input_h264 = TestSupport::LoadFirmwareFile(data.input_filename);
ASSERT_NE(nullptr, input_h264);
video->core()->InitializeDirectInput();
auto nal_units = SplitNalUnits(input_h264->ptr, input_h264->size);
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_data_provider.AppendFrameData(std::move(nal_units));
auto multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
multi_decoder->ReceivedNewInput();
}
EXPECT_EQ(std::future_status::ready,
wait_valid.get_future().wait_for(std::chrono::seconds(10)));
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
auto multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
multi_decoder->DumpStatus();
}
EXPECT_LE(data.expected_frame_count, frame_count);
video->ClearDecoderInstance();
video.reset();
}
static void DecodeUnsplit(const char* input_filename,
uint8_t (*input_hashes)[SHA256_DIGEST_LENGTH], const char* filename) {
FakeOwner owner;
auto video = std::make_unique<AmlogicVideo>(&owner);
ASSERT_TRUE(video);
TestFrameAllocator frame_allocator(video.get());
video->SetDeviceType(DeviceType::kSM1);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
H264TestFrameDataProvider frame_data_provider(video.get());
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
video->SetDefaultInstance(
std::make_unique<H264MultiDecoder>(video.get(), &frame_allocator, &frame_data_provider,
std::nullopt, false),
/*hevc=*/false);
frame_data_provider.set_decoder(static_cast<H264MultiDecoder*>(video->video_decoder()));
frame_allocator.set_decoder(video->video_decoder());
}
frame_allocator.set_pump_function([&video]() {
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
static_cast<H264MultiDecoder*>(video->video_decoder())->PumpOrReschedule();
});
// 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));
uint32_t frame_count = 0;
std::promise<void> wait_valid;
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_allocator.SetFrameReadyNotifier([&video, &frame_count, &wait_valid, input_filename,
filename,
input_hashes](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d\n", frame_count);
EXPECT_EQ(320u, frame->coded_width);
EXPECT_EQ(320u, frame->display_width);
bool is_bear = input_filename == std::string("video_test_data/bear.h264");
if (is_bear) {
EXPECT_EQ(192u, frame->coded_height);
EXPECT_EQ(180u, frame->display_height);
} else {
EXPECT_EQ(240u, frame->coded_height);
EXPECT_EQ(240u, frame->display_height);
}
(void)filename;
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame.get(), filename);
#endif
uint8_t md[SHA256_DIGEST_LENGTH];
HashFrame(frame.get(), md);
EXPECT_EQ(0, memcmp(md, input_hashes[frame_count - 1], sizeof(md)))
<< "Incorrect hash for frame " << frame_count << ": " << StringifyHash(md);
video->AssertVideoDecoderLockHeld();
video->video_decoder()->ReturnFrame(frame);
uint32_t expected_frame_count = is_bear ? 26 : 240;
if (frame_count == expected_frame_count) {
wait_valid.set_value();
}
});
// Initialize must happen after InitializeStreamBuffer or else it may misparse the SPS.
EXPECT_EQ(ZX_OK, video->video_decoder()->Initialize());
}
auto input_h264 = TestSupport::LoadFirmwareFile(input_filename);
ASSERT_NE(nullptr, input_h264);
video->core()->InitializeDirectInput();
auto nal_units = SplitNalUnits(input_h264->ptr, input_h264->size);
std::vector<std::vector<uint8_t>> full_data;
full_data.emplace_back(
std::vector<uint8_t>(input_h264->ptr, input_h264->ptr + input_h264->size));
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_data_provider.AppendFrameData(full_data);
auto multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
multi_decoder->ReceivedNewInput();
}
EXPECT_EQ(std::future_status::ready,
wait_valid.get_future().wait_for(std::chrono::seconds(10)));
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
auto multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
multi_decoder->DumpStatus();
}
// Try to make sure no more frames are decoded.
zx_nanosleep(zx_deadline_after(ZX_SEC(1)));
EXPECT_EQ(28u, frame_count);
video->ClearDecoderInstance();
video.reset();
}
static void TestInitializeTwice() {
FakeOwner owner;
auto video = std::make_unique<AmlogicVideo>(&owner);
ASSERT_TRUE(video);
TestFrameAllocator frame_allocator(video.get());
video->SetDeviceType(DeviceType::kSM1);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
H264TestFrameDataProvider frame_data_provider(video.get());
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
video->SetDefaultInstance(
std::make_unique<H264MultiDecoder>(video.get(), &frame_allocator, &frame_data_provider,
std::nullopt, /*is_secure=*/false),
/*hevc=*/false);
frame_data_provider.set_decoder(static_cast<H264MultiDecoder*>(video->video_decoder()));
frame_allocator.set_decoder(video->video_decoder());
}
EXPECT_EQ(ZX_OK, video->InitializeStreamBuffer(/*use_parser=*/false, 1024 * PAGE_SIZE,
/*is_secure=*/false));
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
EXPECT_EQ(ZX_OK, video->video_decoder()->Initialize());
auto* decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
void* virt_base_1 = decoder->SecondaryFirmwareVirtualAddressForTesting();
decoder->SetSwappedOut();
EXPECT_EQ(ZX_OK, video->video_decoder()->InitializeHardware());
EXPECT_EQ(virt_base_1, decoder->SecondaryFirmwareVirtualAddressForTesting());
}
video.reset();
}
static void DecodeMultiInstance() {
FakeOwner owner;
auto video = std::make_unique<AmlogicVideo>(&owner);
ASSERT_TRUE(video);
video->SetDeviceType(DeviceType::kSM1);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
std::vector<std::unique_ptr<TestFrameAllocator>> clients;
std::vector<std::unique_ptr<H264TestFrameDataProvider>> providers;
std::vector<H264MultiDecoder*> decoder_ptrs;
for (uint32_t i = 0; i < 2; i++) {
auto client = std::make_unique<TestFrameAllocator>(video.get());
auto provider = std::make_unique<H264TestFrameDataProvider>(video.get());
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
auto decoder = std::make_unique<H264MultiDecoder>(video.get(), client.get(), provider.get(),
std::nullopt, /*is_secure=*/false);
decoder_ptrs.push_back(decoder.get());
provider->set_decoder(decoder.get());
client->set_decoder(decoder.get());
client->set_pump_function([&video, &decoder_ptrs, i]() {
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
decoder_ptrs[i]->PumpOrReschedule();
});
clients.push_back(std::move(client));
providers.push_back(std::move(provider));
EXPECT_EQ(ZX_OK, decoder->InitializeBuffers());
auto decoder_instance =
std::make_unique<DecoderInstance>(std::move(decoder), video->vdec1_core());
StreamBuffer* buffer = decoder_instance->stream_buffer();
video->AddNewDecoderInstance(std::move(decoder_instance));
EXPECT_EQ(ZX_OK, video->AllocateStreamBuffer(buffer, PAGE_SIZE * 1024, std::nullopt,
/*use_parser=*/false,
/*is_secure=*/false));
}
struct ClientData {
uint32_t frame_count{};
uint32_t expected_frame_count{};
std::promise<void> wait_valid;
uint8_t (*input_hashes)[SHA256_DIGEST_LENGTH];
};
uint32_t last_client_index = UINT32_MAX;
uint32_t context_switch_count = 0;
std::vector<ClientData> client_data(2);
for (uint32_t i = 0; i < 2; i++) {
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
clients[i]->SetFrameReadyNotifier([&video, client_ptr = clients[i].get(), i,
client_data_ptr = &client_data[i], &last_client_index,
&context_switch_count](std::shared_ptr<VideoFrame> frame) {
client_data_ptr->frame_count++;
DLOG("Got frame %d client %d\n", client_data_ptr->frame_count, i);
EXPECT_EQ(320u, frame->coded_width);
EXPECT_EQ(320u, frame->display_width);
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame.get(), filename);
#endif
uint8_t md[SHA256_DIGEST_LENGTH];
HashFrame(frame.get(), md);
EXPECT_EQ(0, memcmp(md, client_data_ptr->input_hashes[client_data_ptr->frame_count - 1],
sizeof(md)))
<< "Incorrect hash for frame " << client_data_ptr->frame_count << ": "
<< StringifyHash(md);
video->AssertVideoDecoderLockHeld();
video->video_decoder()->ReturnFrame(frame);
if (last_client_index != i) {
context_switch_count++;
}
last_client_index = i;
if (client_data_ptr->frame_count == client_data_ptr->expected_frame_count) {
client_data_ptr->wait_valid.set_value();
}
});
}
// Put test-25fps before bear.h264 because it's much longer and has a larger DPB so it takes
// longer to start outputting frames. This way there will be more alternation between them if
// everything works properly.
std::vector<const char*> input_files{"video_test_data/test-25fps.h264",
"video_test_data/bear.h264"};
client_data[0].expected_frame_count = 240;
client_data[0].input_hashes = test_25fps_h264_hashes;
client_data[1].expected_frame_count = 26;
client_data[1].input_hashes = bear_h264_hashes;
for (uint32_t i = 0; i < input_files.size(); ++i) {
auto input_h264 = TestSupport::LoadFirmwareFile(input_files[i]);
ASSERT_NE(nullptr, input_h264);
auto nal_units = SplitNalUnits(input_h264->ptr, input_h264->size);
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
providers[i]->AppendFrameData(std::move(nal_units));
decoder_ptrs[i]->ReceivedNewInput();
}
}
EXPECT_EQ(std::future_status::ready,
client_data[0].wait_valid.get_future().wait_for(std::chrono::seconds(10)));
EXPECT_EQ(std::future_status::ready,
client_data[1].wait_valid.get_future().wait_for(std::chrono::seconds(10)));
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
auto multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
multi_decoder->DumpStatus();
}
// A mostly-arbitrary number to ensure we don't just decode all of one video then all of
// another.
EXPECT_LE(5u, context_switch_count);
for (auto& decoder : decoder_ptrs) {
video->RemoveDecoder(decoder);
}
video.reset();
}
static void DecodeChangeConfig() {
FakeOwner owner;
auto video = std::make_unique<AmlogicVideo>(&owner);
ASSERT_TRUE(video);
TestFrameAllocator frame_allocator(video.get());
video->SetDeviceType(DeviceType::kSM1);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
H264TestFrameDataProvider frame_data_provider(video.get());
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
video->SetDefaultInstance(
std::make_unique<H264MultiDecoder>(video.get(), &frame_allocator, &frame_data_provider,
std::nullopt, /*is_secure=*/false),
/*hevc=*/false);
frame_data_provider.set_decoder(static_cast<H264MultiDecoder*>(video->video_decoder()));
frame_allocator.set_decoder(video->video_decoder());
}
frame_allocator.set_pump_function([&video]() {
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
static_cast<H264MultiDecoder*>(video->video_decoder())->PumpOrReschedule();
});
// 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));
uint32_t frame_count = 0;
std::promise<void> wait_valid;
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_allocator.SetFrameReadyNotifier(
[&video, &frame_count, &wait_valid](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d", frame_count);
EXPECT_EQ(320u, frame->coded_width);
EXPECT_EQ(320u, frame->display_width);
constexpr uint32_t k25fpsVideoLength = 250;
bool is_bear = frame_count > k25fpsVideoLength;
if (is_bear) {
EXPECT_EQ(192u, frame->coded_height);
EXPECT_EQ(180u, frame->display_height);
} else {
EXPECT_EQ(240u, frame->coded_height);
EXPECT_EQ(240u, frame->display_height);
}
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame.get(), "/tmp/changeconfigmultih264.yuv");
#endif
uint32_t in_video_frame_count = is_bear ? frame_count - k25fpsVideoLength : frame_count;
auto hashes = is_bear ? bear_h264_hashes : test_25fps_h264_hashes;
uint64_t max_size =
is_bear ? std::size(bear_h264_hashes) : std::size(test_25fps_h264_hashes);
if (in_video_frame_count <= max_size) {
uint8_t md[SHA256_DIGEST_LENGTH];
HashFrame(frame.get(), md);
EXPECT_EQ(0, memcmp(md, hashes[in_video_frame_count - 1], sizeof(md)))
<< "Incorrect hash for frame " << frame_count << ": " << StringifyHash(md);
}
video->AssertVideoDecoderLockHeld();
video->video_decoder()->ReturnFrame(frame);
if (frame_count == 26 + k25fpsVideoLength) {
wait_valid.set_value();
}
});
// Initialize must happen after InitializeStreamBuffer or else it may misparse the SPS.
EXPECT_EQ(ZX_OK, video->video_decoder()->Initialize());
}
video->core()->InitializeDirectInput();
std::vector<const char*> input_files{"video_test_data/test-25fps.h264",
"video_test_data/bear.h264"};
for (auto* input_filename : input_files) {
auto input_h264 = TestSupport::LoadFirmwareFile(input_filename);
ASSERT_NE(nullptr, input_h264);
auto nal_units = SplitNalUnits(input_h264->ptr, input_h264->size);
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_data_provider.AppendFrameData(std::move(nal_units));
auto multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
multi_decoder->ReceivedNewInput();
}
}
EXPECT_EQ(std::future_status::ready,
wait_valid.get_future().wait_for(std::chrono::seconds(10)));
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
auto multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
multi_decoder->DumpStatus();
}
video->ClearDecoderInstance();
video.reset();
}
static void DecodeWithEos(const char* input_filename,
uint8_t (*input_hashes)[SHA256_DIGEST_LENGTH], const char* filename,
bool early_eos) {
FakeOwner owner;
auto video = std::make_unique<AmlogicVideo>(&owner);
ASSERT_TRUE(video);
TestFrameAllocator frame_allocator(video.get());
video->SetDeviceType(DeviceType::kSM1);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
H264TestFrameDataProvider frame_data_provider(video.get());
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
video->SetDefaultInstance(
std::make_unique<H264MultiDecoder>(video.get(), &frame_allocator, &frame_data_provider,
std::nullopt, /*is_secure=*/false),
/*hevc=*/false);
frame_data_provider.set_decoder(static_cast<H264MultiDecoder*>(video->video_decoder()));
frame_allocator.set_decoder(video->video_decoder());
}
frame_allocator.set_pump_function([&video]() {
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
static_cast<H264MultiDecoder*>(video->video_decoder())->PumpOrReschedule();
});
// 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));
uint32_t frame_count = 0;
std::promise<void> wait_valid;
frame_allocator.SetEosHandler([&wait_valid] { wait_valid.set_value(); });
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_allocator.SetFrameReadyNotifier(
[&video, &frame_count, filename](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d\n", frame_count);
EXPECT_EQ(320u, frame->coded_width);
EXPECT_EQ(320u, frame->display_width);
EXPECT_EQ(192u, frame->coded_height);
EXPECT_EQ(180u, frame->display_height);
(void)filename;
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame.get(), filename);
#endif
video->AssertVideoDecoderLockHeld();
video->video_decoder()->ReturnFrame(frame);
});
// Initialize must happen after InitializeStreamBuffer or else it may misparse the SPS.
EXPECT_EQ(ZX_OK, video->video_decoder()->Initialize());
}
auto input_h264 = TestSupport::LoadFirmwareFile(input_filename);
ASSERT_NE(nullptr, input_h264);
video->core()->InitializeDirectInput();
auto nal_units = SplitNalUnits(input_h264->ptr, input_h264->size);
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_data_provider.AppendFrameData(std::move(nal_units));
auto multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
if (early_eos) {
multi_decoder->QueueInputEos();
}
multi_decoder->ReceivedNewInput();
}
auto future = wait_valid.get_future();
if (!early_eos) {
EXPECT_EQ(std::future_status::timeout, future.wait_for(std::chrono::seconds(2)));
EXPECT_EQ(28u, frame_count);
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
auto* multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
multi_decoder->QueueInputEos();
}
EXPECT_EQ(std::future_status::ready, future.wait_for(std::chrono::seconds(10)));
EXPECT_EQ(30u, frame_count);
video->ClearDecoderInstance();
video.reset();
}
static void DecodeMalformed(VideoInfo data,
const std::vector<std::pair<uint32_t, uint8_t>>& modifications) {
FakeOwner owner;
auto video = std::make_unique<AmlogicVideo>(&owner);
ASSERT_TRUE(video);
TestFrameAllocator frame_allocator(video.get());
video->SetDeviceType(DeviceType::kSM1);
EXPECT_EQ(ZX_OK, video->InitRegisters(TestSupport::parent_device()));
EXPECT_EQ(ZX_OK, video->InitDecoder());
H264TestFrameDataProvider frame_data_provider(video.get());
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
video->SetDefaultInstance(
std::make_unique<H264MultiDecoder>(video.get(), &frame_allocator, &frame_data_provider,
std::nullopt, /*is_secure=*/false),
/*hevc=*/false);
frame_data_provider.set_decoder(static_cast<H264MultiDecoder*>(video->video_decoder()));
frame_allocator.set_decoder(video->video_decoder());
}
frame_allocator.set_pump_function([&video]() {
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
static_cast<H264MultiDecoder*>(video->video_decoder())->PumpOrReschedule();
});
// 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));
uint32_t frame_count = 0;
std::promise<void> wait_valid;
frame_data_provider.set_async_reset_handler([&wait_valid] { wait_valid.set_value(); });
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_allocator.SetFrameReadyNotifier(
[&video, &frame_count](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d\n", frame_count);
video->AssertVideoDecoderLockHeld();
video->video_decoder()->ReturnFrame(frame);
});
// Initialize must happen after InitializeStreamBuffer or else it may misparse the SPS.
EXPECT_EQ(ZX_OK, video->video_decoder()->Initialize());
}
auto input_h264 = TestSupport::LoadFirmwareFile(data.input_filename);
ASSERT_NE(nullptr, input_h264);
video->core()->InitializeDirectInput();
std::vector<uint8_t> modified_data(input_h264->ptr, input_h264->ptr + input_h264->size);
for (auto& modification : modifications) {
modified_data[modification.first] = modification.second;
}
auto nal_units = SplitNalUnits(modified_data.data(), modified_data.size());
{
std::lock_guard<std::mutex> lock(*video->video_decoder_lock());
frame_data_provider.AppendFrameData(std::move(nal_units));
auto multi_decoder = static_cast<H264MultiDecoder*>(video->video_decoder());
multi_decoder->ReceivedNewInput();
}
auto future = wait_valid.get_future();
EXPECT_EQ(std::future_status::ready, future.wait_for(std::chrono::seconds(10)));
EXPECT_EQ(0u, frame_count);
video->ClearDecoderInstance();
video.reset();
}
};
static TestH264Multi::VideoInfo bear_data = {
.input_filename = "video_test_data/bear.h264",
.input_hashes = bear_h264_hashes,
.filename = "/tmp/bearmultih264.yuv",
.coded_width = 320,
.coded_height = 192,
.display_width = 320,
.display_height = 180,
.expected_frame_count = 28,
.has_sar = false,
};
TEST(H264Multi, DecodeBear) {
TestH264Multi::DecodeSetStream(bear_data,
/*use_parser=*/false);
}
TEST(H264Multi, DecodeBearParser) {
TestH264Multi::DecodeSetStream(bear_data,
/*use_parser=*/true);
}
TEST(H264Multi, Decode25fps) {
TestH264Multi::VideoInfo data = {.input_filename = "video_test_data/test-25fps.h264",
.input_hashes = test_25fps_h264_hashes,
.filename = "/tmp/test25fpsmultih264.yuv",
.coded_width = 320,
.coded_height = 240,
.display_width = 320,
.display_height = 240,
.expected_frame_count = 240,
.has_sar = false};
TestH264Multi::DecodeSetStream(data,
/*use_parser=*/false);
}
TEST(H264Multi, DecodeWithSar) {
TestH264Multi::VideoInfo data = {.input_filename = "video_test_data/red-green.h264",
.input_hashes = nullptr,
.filename = "/tmp/red-greenmultih264.yuv",
.coded_width = 80,
.coded_height = 128,
.display_width = 80,
.display_height = 128,
.expected_frame_count = 28,
.has_sar = true};
TestH264Multi::DecodeSetStream(data,
/*use_parser=*/false);
}
TEST(H264Multi, DecodeBearUnsplit) {
TestH264Multi::DecodeUnsplit("video_test_data/bear.h264", bear_h264_hashes,
"/tmp/bearmultih264.yuv");
}
TEST(H264Multi, InitializeTwice) { TestH264Multi::TestInitializeTwice(); }
TEST(H264Multi, DecodeMultiInstance) { TestH264Multi::DecodeMultiInstance(); }
TEST(H264Multi, DecodeChangeConfig) { TestH264Multi::DecodeChangeConfig(); }
TEST(H264Multi, DecodeWithEarlyEos) {
TestH264Multi::DecodeWithEos("video_test_data/bear.h264", bear_h264_hashes,
"/tmp/bearmultih264.yuv", /*early_eos=*/true);
}
TEST(H264Multi, DecodeWithLateEos) {
TestH264Multi::DecodeWithEos("video_test_data/bear.h264", bear_h264_hashes,
"/tmp/bearmultih264.yuv", /*early_eos=*/false);
}
TEST(H264Multi, DecodeMalformedSize) {
// This changes the height to 53184, which is too high for the hardware.
TestH264Multi::DecodeMalformed(bear_data, {{593, 64}});
}
} // namespace test
} // namespace amlogic_decoder