Add support for NV12 frame generator
Bug: b/240540204
Change-Id: Id2205e8bd0dfd59476dcd68c32c4981f98b51422
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/278402
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38322}
diff --git a/api/test/create_frame_generator.cc b/api/test/create_frame_generator.cc
index 7ed0647..5e6fb32 100644
--- a/api/test/create_frame_generator.cc
+++ b/api/test/create_frame_generator.cc
@@ -47,6 +47,23 @@
frame_repeat_count);
}
+std::unique_ptr<FrameGeneratorInterface> CreateFromNV12FileFrameGenerator(
+ std::vector<std::string> filenames,
+ size_t width,
+ size_t height,
+ int frame_repeat_count) {
+ RTC_DCHECK(!filenames.empty());
+ std::vector<FILE*> files;
+ for (const std::string& filename : filenames) {
+ FILE* file = fopen(filename.c_str(), "rb");
+ RTC_DCHECK(file != nullptr) << "Failed to open: '" << filename << "'\n";
+ files.push_back(file);
+ }
+
+ return std::make_unique<NV12FileGenerator>(files, width, height,
+ frame_repeat_count);
+}
+
std::unique_ptr<FrameGeneratorInterface> CreateFromIvfFileFrameGenerator(
std::string filename) {
return std::make_unique<IvfVideoFrameGenerator>(std::move(filename));
diff --git a/api/test/create_frame_generator.h b/api/test/create_frame_generator.h
index cd4fccc..70be0c4 100644
--- a/api/test/create_frame_generator.h
+++ b/api/test/create_frame_generator.h
@@ -41,6 +41,15 @@
size_t height,
int frame_repeat_count);
+// Creates a frame generator that repeatedly plays a set of nv12 files.
+// The frame_repeat_count determines how many times each frame is shown,
+// with 1 = show each frame once, etc.
+std::unique_ptr<FrameGeneratorInterface> CreateFromNV12FileFrameGenerator(
+ std::vector<std::string> filenames,
+ size_t width,
+ size_t height,
+ int frame_repeat_count = 1);
+
// Creates a frame generator that repeatedly plays an ivf file.
std::unique_ptr<FrameGeneratorInterface> CreateFromIvfFileFrameGenerator(
std::string filename);
diff --git a/common_video/libyuv/include/webrtc_libyuv.h b/common_video/libyuv/include/webrtc_libyuv.h
index d7939dc..08a035a 100644
--- a/common_video/libyuv/include/webrtc_libyuv.h
+++ b/common_video/libyuv/include/webrtc_libyuv.h
@@ -39,6 +39,7 @@
kUYVY,
kMJPEG,
kBGRA,
+ kNV12,
};
// This is the max PSNR value our algorithms can return.
diff --git a/common_video/libyuv/webrtc_libyuv.cc b/common_video/libyuv/webrtc_libyuv.cc
index 51a766c..14e2d22 100644
--- a/common_video/libyuv/webrtc_libyuv.cc
+++ b/common_video/libyuv/webrtc_libyuv.cc
@@ -26,7 +26,8 @@
switch (type) {
case VideoType::kI420:
case VideoType::kIYUV:
- case VideoType::kYV12: {
+ case VideoType::kYV12:
+ case VideoType::kNV12: {
int half_width = (width + 1) >> 1;
int half_height = (height + 1) >> 1;
buffer_size = width * height + half_width * half_height * 2;
@@ -105,6 +106,8 @@
return libyuv::FOURCC_ARGB;
case VideoType::kBGRA:
return libyuv::FOURCC_BGRA;
+ case VideoType::kNV12:
+ return libyuv::FOURCC_NV12;
}
RTC_DCHECK_NOTREACHED();
return libyuv::FOURCC_ANY;
diff --git a/test/frame_generator.cc b/test/frame_generator.cc
index 92e95f6..b6f16a5 100644
--- a/test/frame_generator.cc
+++ b/test/frame_generator.cc
@@ -205,6 +205,68 @@
return frame_index_ != prev_frame_index || file_index_ != prev_file_index;
}
+NV12FileGenerator::NV12FileGenerator(std::vector<FILE*> files,
+ size_t width,
+ size_t height,
+ int frame_repeat_count)
+ : file_index_(0),
+ frame_index_(std::numeric_limits<size_t>::max()),
+ files_(files),
+ width_(width),
+ height_(height),
+ frame_size_(CalcBufferSize(VideoType::kNV12,
+ static_cast<int>(width_),
+ static_cast<int>(height_))),
+ frame_buffer_(new uint8_t[frame_size_]),
+ frame_display_count_(frame_repeat_count),
+ current_display_count_(0) {
+ RTC_DCHECK_GT(width, 0);
+ RTC_DCHECK_GT(height, 0);
+ RTC_DCHECK_GT(frame_repeat_count, 0);
+}
+
+NV12FileGenerator::~NV12FileGenerator() {
+ for (FILE* file : files_)
+ fclose(file);
+}
+
+FrameGeneratorInterface::VideoFrameData NV12FileGenerator::NextFrame() {
+ // Empty update by default.
+ VideoFrame::UpdateRect update_rect{0, 0, 0, 0};
+ if (current_display_count_ == 0) {
+ const bool got_new_frame = ReadNextFrame();
+ // Full update on a new frame from file.
+ if (got_new_frame) {
+ update_rect = VideoFrame::UpdateRect{0, 0, static_cast<int>(width_),
+ static_cast<int>(height_)};
+ }
+ }
+ if (++current_display_count_ >= frame_display_count_)
+ current_display_count_ = 0;
+
+ return VideoFrameData(last_read_buffer_, update_rect);
+}
+
+bool NV12FileGenerator::ReadNextFrame() {
+ size_t prev_frame_index = frame_index_;
+ size_t prev_file_index = file_index_;
+ last_read_buffer_ = test::ReadNV12Buffer(
+ static_cast<int>(width_), static_cast<int>(height_), files_[file_index_]);
+ ++frame_index_;
+ if (!last_read_buffer_) {
+ // No more frames to read in this file, rewind and move to next file.
+ rewind(files_[file_index_]);
+
+ frame_index_ = 0;
+ file_index_ = (file_index_ + 1) % files_.size();
+ last_read_buffer_ =
+ test::ReadNV12Buffer(static_cast<int>(width_),
+ static_cast<int>(height_), files_[file_index_]);
+ RTC_CHECK(last_read_buffer_);
+ }
+ return frame_index_ != prev_frame_index || file_index_ != prev_file_index;
+}
+
SlideGenerator::SlideGenerator(int width, int height, int frame_repeat_count)
: width_(width),
height_(height),
diff --git a/test/frame_generator.h b/test/frame_generator.h
index 3e2a4cb..9a8f08c 100644
--- a/test/frame_generator.h
+++ b/test/frame_generator.h
@@ -17,6 +17,7 @@
#include "api/scoped_refptr.h"
#include "api/test/frame_generator_interface.h"
#include "api/video/i420_buffer.h"
+#include "api/video/nv12_buffer.h"
#include "api/video/video_frame.h"
#include "api/video/video_frame_buffer.h"
#include "api/video/video_source_interface.h"
@@ -76,8 +77,7 @@
VideoFrameData NextFrame() override;
void ChangeResolution(size_t width, size_t height) override {
- RTC_LOG(LS_WARNING)
- << "ScrollingImageFrameGenerator::ChangeResolution not implemented";
+ RTC_LOG(LS_WARNING) << "YuvFileGenerator::ChangeResolution not implemented";
}
private:
@@ -97,6 +97,38 @@
rtc::scoped_refptr<I420Buffer> last_read_buffer_;
};
+class NV12FileGenerator : public FrameGeneratorInterface {
+ public:
+ NV12FileGenerator(std::vector<FILE*> files,
+ size_t width,
+ size_t height,
+ int frame_repeat_count);
+
+ ~NV12FileGenerator();
+
+ VideoFrameData NextFrame() override;
+ void ChangeResolution(size_t width, size_t height) override {
+ RTC_LOG(LS_WARNING)
+ << "NV12FileGenerator::ChangeResolution not implemented";
+ }
+
+ private:
+ // Returns true if the new frame was loaded.
+ // False only in case of a single file with a single frame in it.
+ bool ReadNextFrame();
+
+ size_t file_index_;
+ size_t frame_index_;
+ const std::vector<FILE*> files_;
+ const size_t width_;
+ const size_t height_;
+ const size_t frame_size_;
+ const std::unique_ptr<uint8_t[]> frame_buffer_;
+ const int frame_display_count_;
+ int current_display_count_;
+ rtc::scoped_refptr<NV12Buffer> last_read_buffer_;
+};
+
// SlideGenerator works similarly to YuvFileGenerator but it fills the frames
// with randomly sized and colored squares instead of reading their content
// from files.
diff --git a/test/frame_generator_unittest.cc b/test/frame_generator_unittest.cc
index 467323a..ece37a5 100644
--- a/test/frame_generator_unittest.cc
+++ b/test/frame_generator_unittest.cc
@@ -27,28 +27,44 @@
namespace webrtc {
namespace test {
-static const int kFrameWidth = 4;
-static const int kFrameHeight = 4;
+constexpr int kFrameWidth = 4;
+constexpr int kFrameHeight = 4;
+constexpr int y_size = kFrameWidth * kFrameHeight;
+constexpr int uv_size = ((kFrameHeight + 1) / 2) * ((kFrameWidth + 1) / 2);
class FrameGeneratorTest : public ::testing::Test {
public:
void SetUp() override {
- two_frame_filename_ =
+ two_frame_yuv_filename_ =
test::TempFilename(test::OutputPath(), "2_frame_yuv_file");
- one_frame_filename_ =
+ one_frame_yuv_filename_ =
test::TempFilename(test::OutputPath(), "1_frame_yuv_file");
+ two_frame_nv12_filename_ =
+ test::TempFilename(test::OutputPath(), "2_frame_nv12_file");
+ one_frame_nv12_filename_ =
+ test::TempFilename(test::OutputPath(), "1_frame_nv12_file");
- FILE* file = fopen(two_frame_filename_.c_str(), "wb");
+ FILE* file = fopen(two_frame_yuv_filename_.c_str(), "wb");
WriteYuvFile(file, 0, 0, 0);
- WriteYuvFile(file, 127, 127, 127);
+ WriteYuvFile(file, 127, 128, 129);
fclose(file);
- file = fopen(one_frame_filename_.c_str(), "wb");
+ file = fopen(one_frame_yuv_filename_.c_str(), "wb");
WriteYuvFile(file, 255, 255, 255);
fclose(file);
+ file = fopen(two_frame_nv12_filename_.c_str(), "wb");
+ WriteNV12File(file, 0, 0, 0);
+ WriteNV12File(file, 127, 128, 129);
+ fclose(file);
+ file = fopen(one_frame_nv12_filename_.c_str(), "wb");
+ WriteNV12File(file, 255, 255, 255);
+ fclose(file);
}
+
void TearDown() override {
- remove(one_frame_filename_.c_str());
- remove(two_frame_filename_.c_str());
+ remove(one_frame_yuv_filename_.c_str());
+ remove(two_frame_yuv_filename_.c_str());
+ remove(one_frame_nv12_filename_.c_str());
+ remove(two_frame_nv12_filename_.c_str());
}
protected:
@@ -63,6 +79,19 @@
fwrite(plane_buffer.get(), 1, uv_size, file);
}
+ void WriteNV12File(FILE* file, uint8_t y, uint8_t u, uint8_t v) {
+ RTC_DCHECK(file);
+ uint8_t plane_buffer[y_size];
+
+ memset(&plane_buffer, y, y_size);
+ fwrite(&plane_buffer, 1, y_size, file);
+ for (size_t i = 0; i < uv_size; ++i) {
+ plane_buffer[2 * i] = u;
+ plane_buffer[2 * i + 1] = v;
+ }
+ fwrite(&plane_buffer, 1, 2 * uv_size, file);
+ }
+
void CheckFrameAndMutate(const FrameGeneratorInterface::VideoFrameData& frame,
uint8_t y,
uint8_t u,
@@ -102,69 +131,131 @@
return hash;
}
- std::string two_frame_filename_;
- std::string one_frame_filename_;
- const int y_size = kFrameWidth * kFrameHeight;
- const int uv_size = ((kFrameHeight + 1) / 2) * ((kFrameWidth + 1) / 2);
+ std::string two_frame_yuv_filename_;
+ std::string one_frame_yuv_filename_;
+ std::string two_frame_nv12_filename_;
+ std::string one_frame_nv12_filename_;
};
-TEST_F(FrameGeneratorTest, SingleFrameFile) {
+TEST_F(FrameGeneratorTest, SingleFrameYuvFile) {
std::unique_ptr<FrameGeneratorInterface> generator(
CreateFromYuvFileFrameGenerator(
- std::vector<std::string>(1, one_frame_filename_), kFrameWidth,
+ std::vector<std::string>(1, one_frame_yuv_filename_), kFrameWidth,
kFrameHeight, 1));
CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255);
CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255);
}
-TEST_F(FrameGeneratorTest, TwoFrameFile) {
+TEST_F(FrameGeneratorTest, TwoFrameYuvFile) {
std::unique_ptr<FrameGeneratorInterface> generator(
CreateFromYuvFileFrameGenerator(
- std::vector<std::string>(1, two_frame_filename_), kFrameWidth,
+ std::vector<std::string>(1, two_frame_yuv_filename_), kFrameWidth,
kFrameHeight, 1));
CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
- CheckFrameAndMutate(generator->NextFrame(), 127, 127, 127);
+ CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129);
CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
}
-TEST_F(FrameGeneratorTest, MultipleFrameFiles) {
+TEST_F(FrameGeneratorTest, MultipleFrameYuvFiles) {
std::vector<std::string> files;
- files.push_back(two_frame_filename_);
- files.push_back(one_frame_filename_);
+ files.push_back(two_frame_yuv_filename_);
+ files.push_back(one_frame_yuv_filename_);
std::unique_ptr<FrameGeneratorInterface> generator(
CreateFromYuvFileFrameGenerator(files, kFrameWidth, kFrameHeight, 1));
CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
- CheckFrameAndMutate(generator->NextFrame(), 127, 127, 127);
+ CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129);
CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255);
CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
}
-TEST_F(FrameGeneratorTest, TwoFrameFileWithRepeat) {
+TEST_F(FrameGeneratorTest, TwoFrameYuvFileWithRepeat) {
const int kRepeatCount = 3;
std::unique_ptr<FrameGeneratorInterface> generator(
CreateFromYuvFileFrameGenerator(
- std::vector<std::string>(1, two_frame_filename_), kFrameWidth,
+ std::vector<std::string>(1, two_frame_yuv_filename_), kFrameWidth,
kFrameHeight, kRepeatCount));
for (int i = 0; i < kRepeatCount; ++i)
CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
for (int i = 0; i < kRepeatCount; ++i)
- CheckFrameAndMutate(generator->NextFrame(), 127, 127, 127);
+ CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129);
CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
}
-TEST_F(FrameGeneratorTest, MultipleFrameFilesWithRepeat) {
+TEST_F(FrameGeneratorTest, MultipleFrameYuvFilesWithRepeat) {
const int kRepeatCount = 3;
std::vector<std::string> files;
- files.push_back(two_frame_filename_);
- files.push_back(one_frame_filename_);
+ files.push_back(two_frame_yuv_filename_);
+ files.push_back(one_frame_yuv_filename_);
std::unique_ptr<FrameGeneratorInterface> generator(
CreateFromYuvFileFrameGenerator(files, kFrameWidth, kFrameHeight,
kRepeatCount));
for (int i = 0; i < kRepeatCount; ++i)
CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
for (int i = 0; i < kRepeatCount; ++i)
- CheckFrameAndMutate(generator->NextFrame(), 127, 127, 127);
+ CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129);
+ for (int i = 0; i < kRepeatCount; ++i)
+ CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255);
+ CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
+}
+
+TEST_F(FrameGeneratorTest, SingleFrameNV12File) {
+ std::unique_ptr<FrameGeneratorInterface> generator(
+ CreateFromNV12FileFrameGenerator(
+ std::vector<std::string>(1, one_frame_nv12_filename_), kFrameWidth,
+ kFrameHeight, 1));
+ CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255);
+ CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255);
+}
+
+TEST_F(FrameGeneratorTest, TwoFrameNV12File) {
+ std::unique_ptr<FrameGeneratorInterface> generator(
+ CreateFromNV12FileFrameGenerator(
+ std::vector<std::string>(1, two_frame_nv12_filename_), kFrameWidth,
+ kFrameHeight, 1));
+ CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
+ CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129);
+ CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
+}
+
+TEST_F(FrameGeneratorTest, MultipleFrameNV12Files) {
+ std::vector<std::string> files;
+ files.push_back(two_frame_nv12_filename_);
+ files.push_back(one_frame_nv12_filename_);
+
+ std::unique_ptr<FrameGeneratorInterface> generator(
+ CreateFromNV12FileFrameGenerator(files, kFrameWidth, kFrameHeight, 1));
+ CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
+ CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129);
+ CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255);
+ CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
+}
+
+TEST_F(FrameGeneratorTest, TwoFrameNV12FileWithRepeat) {
+ const int kRepeatCount = 3;
+ std::unique_ptr<FrameGeneratorInterface> generator(
+ CreateFromNV12FileFrameGenerator(
+ std::vector<std::string>(1, two_frame_nv12_filename_), kFrameWidth,
+ kFrameHeight, kRepeatCount));
+ for (int i = 0; i < kRepeatCount; ++i)
+ CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
+ for (int i = 0; i < kRepeatCount; ++i)
+ CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129);
+ CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
+}
+
+TEST_F(FrameGeneratorTest, MultipleFrameNV12FilesWithRepeat) {
+ const int kRepeatCount = 3;
+ std::vector<std::string> files;
+ files.push_back(two_frame_nv12_filename_);
+ files.push_back(one_frame_nv12_filename_);
+ std::unique_ptr<FrameGeneratorInterface> generator(
+ CreateFromNV12FileFrameGenerator(files, kFrameWidth, kFrameHeight,
+ kRepeatCount));
+ for (int i = 0; i < kRepeatCount; ++i)
+ CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
+ for (int i = 0; i < kRepeatCount; ++i)
+ CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129);
for (int i = 0; i < kRepeatCount; ++i)
CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255);
CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
diff --git a/test/frame_utils.cc b/test/frame_utils.cc
index 30389fb..b280de1 100644
--- a/test/frame_utils.cc
+++ b/test/frame_utils.cc
@@ -14,6 +14,7 @@
#include <string.h>
#include "api/video/i420_buffer.h"
+#include "api/video/nv12_buffer.h"
#include "api/video/video_frame.h"
namespace webrtc {
@@ -87,5 +88,17 @@
return buffer;
}
+rtc::scoped_refptr<NV12Buffer> ReadNV12Buffer(int width, int height, FILE* f) {
+ rtc::scoped_refptr<NV12Buffer> buffer(NV12Buffer::Create(width, height));
+ size_t size_y = static_cast<size_t>(width) * height;
+ size_t size_uv = static_cast<size_t>(width + width % 2) * ((height + 1) / 2);
+
+ if (fread(buffer->MutableDataY(), 1, size_y, f) < size_y)
+ return nullptr;
+ if (fread(buffer->MutableDataUV(), 1, size_uv, f) < size_uv)
+ return nullptr;
+ return buffer;
+}
+
} // namespace test
} // namespace webrtc
diff --git a/test/frame_utils.h b/test/frame_utils.h
index 9351584..1f2b381 100644
--- a/test/frame_utils.h
+++ b/test/frame_utils.h
@@ -13,6 +13,7 @@
#include <stdint.h>
#include "api/scoped_refptr.h"
+#include "api/video/nv12_buffer.h"
namespace webrtc {
class I420Buffer;
@@ -42,6 +43,8 @@
rtc::scoped_refptr<I420Buffer> ReadI420Buffer(int width, int height, FILE*);
+rtc::scoped_refptr<NV12Buffer> ReadNV12Buffer(int width, int height, FILE*);
+
} // namespace test
} // namespace webrtc