blob: bda0a4429295aa3d6052f799de8f6d6256c50915 [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 <zircon/compiler.h>
#include "amlogic-video.h"
#include "gtest/gtest.h"
#include "h264_decoder.h"
#include "tests/test_support.h"
#include "macros.h"
#include "pts_manager.h"
#include "vdec1.h"
std::vector<std::vector<uint8_t>> SplitNalUnits(const uint8_t* start_data,
uint32_t size) {
std::vector<std::vector<uint8_t>> out_vector;
const uint8_t* this_nal_start = start_data;
while (true) {
if (size < 3)
return out_vector;
uint8_t start_code[3] = {0, 0, 1};
// Add 2 to ensure the next start code found isn't the start of this nal
// unit.
uint8_t* next_nal_start = static_cast<uint8_t*>(
memmem(this_nal_start + 2, size - 2, start_code, sizeof(start_code)));
if (next_nal_start && next_nal_start[-1] == 0)
next_nal_start--;
uint32_t data_size =
next_nal_start ? next_nal_start - this_nal_start : size;
if (data_size > 0) {
std::vector<uint8_t> new_data(data_size);
memcpy(new_data.data(), this_nal_start, data_size);
out_vector.push_back(std::move(new_data));
}
if (!next_nal_start) {
return out_vector;
}
size -= data_size;
this_nal_start = next_nal_start;
}
}
uint8_t GetNalUnitType(const std::vector<uint8_t>& nal_unit) {
// Also works with 4-byte startcodes.
uint8_t start_code[3] = {0, 0, 1};
uint8_t* next_start =
static_cast<uint8_t*>(memmem(nal_unit.data(), nal_unit.size(), start_code,
sizeof(start_code))) +
sizeof(start_code);
return *next_start & 0xf;
}
class TestH264 {
public:
static void Decode(bool use_parser) {
auto video = std::make_unique<AmlogicVideo>();
ASSERT_TRUE(video);
auto bear_h264 = TestSupport::LoadFirmwareFile("video_test_data/bear.h264");
ASSERT_NE(nullptr, bear_h264);
auto larger_h264 =
TestSupport::LoadFirmwareFile("video_test_data/test-25fps.h264");
ASSERT_NE(nullptr, larger_h264);
zx_status_t status = video->InitRegisters(TestSupport::parent_device());
EXPECT_EQ(ZX_OK, status);
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->SetDefaultInstance(std::make_unique<H264Decoder>(video.get()),
false);
}
status = video->InitializeStreamBuffer(
use_parser, use_parser ? PAGE_SIZE : PAGE_SIZE * 1024);
video->InitializeInterrupts();
EXPECT_EQ(ZX_OK, status);
std::promise<void> first_wait_valid;
std::promise<void> second_wait_valid;
uint32_t frame_count = 0;
constexpr uint32_t kFirstVideoFrameCount = 26;
constexpr uint32_t kSecondVideoFrameCount = 244;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
EXPECT_EQ(ZX_OK, video->video_decoder_->Initialize());
video->video_decoder_->SetFrameReadyNotifier(
[&video, &frame_count, &first_wait_valid,
&second_wait_valid](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d width: %d height: %d\n", frame_count,
frame->width, frame->height);
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame, "/tmp/bearh264.yuv");
#endif
if (frame_count == kFirstVideoFrameCount)
first_wait_valid.set_value();
if (frame_count == kFirstVideoFrameCount + kSecondVideoFrameCount)
second_wait_valid.set_value();
ReturnFrame(video.get(), frame);
});
}
if (use_parser) {
EXPECT_EQ(ZX_OK, video->InitializeEsParser());
EXPECT_EQ(ZX_OK, video->ParseVideo(bear_h264->ptr, bear_h264->size));
EXPECT_EQ(ZX_OK, video->WaitForParsingCompleted(ZX_SEC(10)));
} else {
video->core_->InitializeDirectInput();
EXPECT_EQ(ZX_OK,
video->ProcessVideoNoParser(bear_h264->ptr, bear_h264->size));
}
EXPECT_EQ(std::future_status::ready,
first_wait_valid.get_future().wait_for(std::chrono::seconds(1)));
if (use_parser) {
EXPECT_EQ(ZX_OK, video->ParseVideo(larger_h264->ptr, larger_h264->size));
EXPECT_EQ(ZX_OK, video->WaitForParsingCompleted(ZX_SEC(10)));
} else {
EXPECT_EQ(ZX_OK, video->ProcessVideoNoParser(larger_h264->ptr,
larger_h264->size));
}
EXPECT_EQ(std::future_status::ready,
second_wait_valid.get_future().wait_for(std::chrono::seconds(1)));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
EXPECT_EQ(kFirstVideoFrameCount + kSecondVideoFrameCount, frame_count);
video.reset();
}
static void DelayedReturn() {
auto video = std::make_unique<AmlogicVideo>();
ASSERT_TRUE(video);
zx_status_t status = video->InitRegisters(TestSupport::parent_device());
EXPECT_EQ(ZX_OK, status);
auto bear_h264 = TestSupport::LoadFirmwareFile("video_test_data/bear.h264");
ASSERT_NE(nullptr, bear_h264);
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->SetDefaultInstance(std::make_unique<H264Decoder>(video.get()),
false);
}
status = video->InitializeStreamBuffer(false, PAGE_SIZE);
video->InitializeInterrupts();
EXPECT_EQ(ZX_OK, status);
std::promise<void> wait_valid;
// Guarded by decoder lock.
std::vector<std::shared_ptr<VideoFrame>> frames_to_return;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
EXPECT_EQ(ZX_OK, video->video_decoder_->Initialize());
uint32_t frame_count = 0;
video->video_decoder_->SetFrameReadyNotifier(
[&frames_to_return, &frame_count,
&wait_valid](std::shared_ptr<VideoFrame> frame) {
++frame_count;
EXPECT_EQ(320u, frame->display_width);
EXPECT_EQ(180u, frame->display_height);
DLOG("Got frame %d width: %d height: %d\n", frame_count,
frame->width, frame->height);
constexpr uint32_t kFirstVideoFrameCount = 26;
if (frame_count == kFirstVideoFrameCount)
wait_valid.set_value();
frames_to_return.push_back(frame);
});
}
std::atomic<bool> stop_parsing(false);
video->core_->InitializeDirectInput();
auto as = std::async([&video, &bear_h264, &stop_parsing]() {
uint32_t current_offset = 0;
uint8_t* data = bear_h264->ptr;
while (!stop_parsing) {
uint32_t processed_data;
EXPECT_EQ(ZX_OK,
video->ProcessVideoNoParser(data + current_offset,
bear_h264->size - current_offset,
&processed_data));
current_offset += processed_data;
if (current_offset == bear_h264->size)
break;
zx_nanosleep(zx_deadline_after(ZX_MSEC(15)));
}
});
zx_nanosleep(zx_deadline_after(ZX_SEC(1)));
{
DLOG("Returning frames\n");
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
for (auto frame : frames_to_return) {
video->video_decoder_->ReturnFrame(frame);
}
}
EXPECT_EQ(std::future_status::ready,
wait_valid.get_future().wait_for(std::chrono::seconds(1)));
stop_parsing = true;
as.wait();
video.reset();
}
static void DecodeNalUnits(bool use_parser) {
auto video = std::make_unique<AmlogicVideo>();
ASSERT_TRUE(video);
zx_status_t status = video->InitRegisters(TestSupport::parent_device());
EXPECT_EQ(ZX_OK, status);
auto bear_h264 = TestSupport::LoadFirmwareFile("video_test_data/bear.h264");
ASSERT_NE(nullptr, bear_h264);
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
video->SetDefaultInstance(std::make_unique<H264Decoder>(video.get()),
false);
}
status = video->InitializeStreamBuffer(
use_parser, use_parser ? PAGE_SIZE : PAGE_SIZE * 1024);
video->InitializeInterrupts();
EXPECT_EQ(ZX_OK, status);
std::promise<void> first_wait_valid;
std::set<uint64_t> received_pts_set;
{
std::lock_guard<std::mutex> lock(video->video_decoder_lock_);
EXPECT_EQ(ZX_OK, video->video_decoder_->Initialize());
uint32_t frame_count = 0;
video->video_decoder_->SetFrameReadyNotifier(
[&video, &frame_count, &first_wait_valid,
&received_pts_set](std::shared_ptr<VideoFrame> frame) {
++frame_count;
DLOG("Got frame %d width: %d height: %d\n", frame_count,
frame->width, frame->height);
#if DUMP_VIDEO_TO_FILE
DumpVideoFrameToFile(frame, "/tmp/bearh264.yuv");
#endif
constexpr uint32_t kFirstVideoFrameCount = 26;
if (frame_count == kFirstVideoFrameCount)
first_wait_valid.set_value();
ReturnFrame(video.get(), frame);
EXPECT_TRUE(frame->has_pts);
// In the test video the decode order isn't exactly the same as the
// presentation order, so allow the current PTS to be 2 frames
// older then the last received.
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);
});
}
auto split_nal = SplitNalUnits(bear_h264->ptr, bear_h264->size);
uint32_t parsed_video_size = 0;
uint64_t pts_count = 0;
if (use_parser) {
EXPECT_EQ(ZX_OK, video->InitializeEsParser());
} else {
video->core_->InitializeDirectInput();
}
uint32_t total_size = 0;
for (auto& nal : split_nal) {
total_size += nal.size();
}
EXPECT_EQ(bear_h264->size, total_size);
for (auto& nal : split_nal) {
uint8_t nal_type = GetNalUnitType(nal);
if (nal_type == 1 || nal_type == 5) {
video->pts_manager()->InsertPts(parsed_video_size, true, pts_count++);
}
if (use_parser) {
EXPECT_EQ(ZX_OK, video->ParseVideo(nal.data(), nal.size()));
EXPECT_EQ(ZX_OK, video->WaitForParsingCompleted(ZX_SEC(10)));
} else {
EXPECT_EQ(ZX_OK, video->ProcessVideoNoParser(nal.data(), nal.size()));
}
parsed_video_size += nal.size();
}
EXPECT_EQ(std::future_status::ready,
first_wait_valid.get_future().wait_for(std::chrono::seconds(1)));
for (uint32_t i = 0; i < 27; i++) {
// Frame 25 isn't flushed out of the decoder.
if (i != 25)
EXPECT_TRUE(received_pts_set.count(i));
}
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)
__TA_NO_THREAD_SAFETY_ANALYSIS {
video->video_decoder_->ReturnFrame(frame);
}
};
TEST(H264, Decode) { TestH264::Decode(true); }
TEST(H264, DecodeNoParser) { TestH264::Decode(false); }
TEST(H264, DelayedReturn) { TestH264::DelayedReturn(); }
TEST(H264, DecodeNalUnits) { TestH264::DecodeNalUnits(true); }
TEST(H264, DecodeNalUnitsNoParser) { TestH264::DecodeNalUnits(false); }