media: add YUV420p module

Converts RGBA image to YUV420p, useful for most video
codecs.
This commit is contained in:
James R 2023-02-12 02:09:35 -08:00
parent 650264ea86
commit 1415254131
3 changed files with 200 additions and 0 deletions

View file

@ -9,4 +9,6 @@ target_sources(SRB2SDL2 PRIVATE
vorbis.cpp
vorbis.hpp
vorbis_error.hpp
yuv420p.cpp
yuv420p.hpp
)

124
src/media/yuv420p.cpp Normal file
View file

@ -0,0 +1,124 @@
// RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <algorithm>
#include <cstdint>
#include <memory>
#include <libyuv/convert.h>
#include <libyuv/scale_argb.h>
#include <tcb/span.hpp>
#include "../cxxutil.hpp"
#include "yuv420p.hpp"
using namespace srb2::media;
bool YUV420pFrame::BufferRGBA::resize(int width, int height)
{
if (width == width_ && height == height_)
{
return false;
}
width_ = width;
height_ = height;
row_stride = width * 4;
const std::size_t new_size = row_stride * height;
// Overallocate since the vector's alignment can't be
// easily controlled. This is not a significant waste.
vec_.resize(new_size + (kAlignment - 1));
void* p = vec_.data();
std::size_t n = vec_.size();
SRB2_ASSERT(std::align(kAlignment, 1, p, n) != nullptr);
plane = tcb::span<uint8_t>(reinterpret_cast<uint8_t*>(p), new_size);
return true;
}
void YUV420pFrame::BufferRGBA::erase()
{
std::fill(vec_.begin(), vec_.end(), 0);
}
void YUV420pFrame::BufferRGBA::release()
{
if (!vec_.empty())
{
*this = {};
}
}
const VideoFrame::Buffer& YUV420pFrame::rgba_buffer() const
{
return *rgba_;
}
void YUV420pFrame::convert() const
{
// ABGR = RGBA in memory
libyuv::ABGRToI420(
rgba_->plane.data(),
rgba_->row_stride,
y_.plane.data(),
y_.row_stride,
u_.plane.data(),
u_.row_stride,
v_.plane.data(),
v_.row_stride,
width(),
height()
);
}
void YUV420pFrame::scale(const BufferRGBA& scaled_rgba)
{
int vw = scaled_rgba.width();
int vh = scaled_rgba.height();
uint8_t* p = scaled_rgba.plane.data();
const float ru = width() / static_cast<float>(height());
const float rs = vw / static_cast<float>(vh);
// Maintain aspect ratio of unscaled. Fit inside scaled
// aspect by centering image.
if (rs > ru) // scaled is wider
{
vw = vh * ru;
p += (scaled_rgba.width() - vw) / 2 * 4;
}
else
{
vh = vw / ru;
p += (scaled_rgba.height() - vh) / 2 * scaled_rgba.row_stride;
}
// Curiously, this function doesn't care about channel order.
libyuv::ARGBScale(
rgba_->plane.data(),
rgba_->row_stride,
width(),
height(),
p,
scaled_rgba.row_stride,
vw,
vh,
libyuv::FilterMode::kFilterNone
);
rgba_ = &scaled_rgba;
}

74
src/media/yuv420p.hpp Normal file
View file

@ -0,0 +1,74 @@
// RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_MEDIA_YUV420P_HPP__
#define __SRB2_MEDIA_YUV420P_HPP__
#include <cstdint>
#include <vector>
#include "video_frame.hpp"
namespace srb2::media
{
class YUV420pFrame : public VideoFrame
{
public:
// 32-byte aligned for AVX optimizations (see libyuv)
static constexpr int kAlignment = 32;
class BufferRGBA : public VideoFrame::Buffer
{
public:
bool resize(int width, int height); // true if resized
void erase(); // fills with black
void release();
int width() const { return width_; }
int height() const { return height_; }
private:
int width_, height_;
std::vector<uint8_t> vec_;
};
YUV420pFrame(int pts, Buffer y, Buffer u, Buffer v, const BufferRGBA& rgba) :
VideoFrame(pts), y_(y), u_(u), v_(v), rgba_(&rgba)
{
}
~YUV420pFrame() = default;
// Simply resets PTS and RGBA buffer while keeping YUV
// buffers intact.
void reset(int pts, const BufferRGBA& rgba) { *this = YUV420pFrame(pts, y_, u_, v_, rgba); }
// Converts RGBA buffer to YUV planes.
void convert() const;
// Scales the existing buffer into a new one. This new
// buffer replaces the existing one.
void scale(const BufferRGBA& rgba);
virtual int width() const override { return rgba_->width(); }
virtual int height() const override { return rgba_->height(); }
virtual const Buffer& rgba_buffer() const override;
private:
Buffer y_, u_, v_;
const BufferRGBA* rgba_;
};
}; // namespace srb2::media
#endif // __SRB2_MEDIA_YUV420P_HPP__