akvirtualcamera/VCamUtils/src/videoformat.cpp
2021-05-28 18:04:24 -03:00

460 lines
13 KiB
C++

/* akvirtualcamera, virtual camera for Mac and Windows.
* Copyright (C) 2020 Gonzalo Exequiel Pedone
*
* akvirtualcamera is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* akvirtualcamera is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with akvirtualcamera. If not, see <http://www.gnu.org/licenses/>.
*
* Web-Site: http://webcamoid.github.io/
*/
#include <algorithm>
#include <limits>
#include <map>
#include <ostream>
#include "videoformat.h"
#include "utils.h"
namespace AkVCam
{
class VideoFormatPrivate
{
public:
FourCC m_fourcc {0};
int m_width {0};
int m_height {0};
std::vector<Fraction> m_frameRates;
VideoFormatPrivate() = default;
VideoFormatPrivate(FourCC fourcc,
int width,
int height,
const std::vector<Fraction> &frameRates);
};
using PlaneOffsetFunc = size_t (*)(size_t plane, size_t width, size_t height);
using ByplFunc = size_t (*)(size_t plane, size_t width);
class VideoFormatGlobals
{
public:
PixelFormat format;
size_t bpp;
size_t planes;
PlaneOffsetFunc planeOffset;
ByplFunc bypl;
std::string str;
inline static const std::vector<VideoFormatGlobals> &formats();
static inline const VideoFormatGlobals *byPixelFormat(PixelFormat pixelFormat);
static inline const VideoFormatGlobals *byStr(const std::string &str);
static size_t offsetNV(size_t plane, size_t width, size_t height);
static size_t byplNV(size_t plane, size_t width);
template<typename T>
static inline T alignUp(const T &value, const T &align)
{
return (value + align - 1) & ~(align - 1);
}
template<typename T>
static inline T align32(const T &value)
{
return alignUp<T>(value, 32);
}
};
}
AkVCam::VideoFormat::VideoFormat()
{
this->d = new VideoFormatPrivate;
}
AkVCam::VideoFormat::VideoFormat(FourCC fourcc,
int width,
int height,
const std::vector<Fraction> &frameRates)
{
this->d = new VideoFormatPrivate(fourcc, width, height, frameRates);
}
AkVCam::VideoFormat::VideoFormat(const VideoFormat &other)
{
this->d = new VideoFormatPrivate(other.d->m_fourcc,
other.d->m_width,
other.d->m_height,
other.d->m_frameRates);
}
AkVCam::VideoFormat::~VideoFormat()
{
delete this->d;
}
AkVCam::VideoFormat &AkVCam::VideoFormat::operator =(const VideoFormat &other)
{
if (this != &other) {
this->d->m_fourcc = other.d->m_fourcc;
this->d->m_width = other.d->m_width;
this->d->m_height = other.d->m_height;
this->d->m_frameRates = other.d->m_frameRates;
}
return *this;
}
bool AkVCam::VideoFormat::operator ==(const AkVCam::VideoFormat &other) const
{
return this->d->m_fourcc == other.d->m_fourcc
&& this->d->m_width == other.d->m_width
&& this->d->m_height == other.d->m_height
&& this->d->m_frameRates == other.d->m_frameRates;
}
bool AkVCam::VideoFormat::operator !=(const AkVCam::VideoFormat &other) const
{
return this->d->m_fourcc != other.d->m_fourcc
|| this->d->m_width != other.d->m_width
|| this->d->m_height != other.d->m_height
|| this->d->m_frameRates != other.d->m_frameRates;
}
AkVCam::VideoFormat::operator bool() const
{
return this->isValid();
}
AkVCam::FourCC AkVCam::VideoFormat::fourcc() const
{
return this->d->m_fourcc;
}
AkVCam::FourCC &AkVCam::VideoFormat::fourcc()
{
return this->d->m_fourcc;
}
int AkVCam::VideoFormat::width() const
{
return this->d->m_width;
}
int &AkVCam::VideoFormat::width()
{
return this->d->m_width;
}
int AkVCam::VideoFormat::height() const
{
return this->d->m_height;
}
int &AkVCam::VideoFormat::height()
{
return this->d->m_height;
}
std::vector<AkVCam::Fraction> AkVCam::VideoFormat::frameRates() const
{
return this->d->m_frameRates;
}
std::vector<AkVCam::Fraction> &AkVCam::VideoFormat::frameRates()
{
return this->d->m_frameRates;
}
std::vector<AkVCam::FractionRange> AkVCam::VideoFormat::frameRateRanges() const
{
std::vector<FractionRange> ranges;
if (!this->d->m_frameRates.empty()) {
auto min = *std::min_element(this->d->m_frameRates.begin(),
this->d->m_frameRates.end());
auto max = *std::max_element(this->d->m_frameRates.begin(),
this->d->m_frameRates.end());
ranges.emplace_back(FractionRange {min, max});
}
return ranges;
}
AkVCam::Fraction AkVCam::VideoFormat::minimumFrameRate() const
{
if (this->d->m_frameRates.empty())
return {0, 0};
return *std::min_element(this->d->m_frameRates.begin(),
this->d->m_frameRates.end());
}
size_t AkVCam::VideoFormat::bpp() const
{
auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc));
return vf? vf->bpp: 0;
}
size_t AkVCam::VideoFormat::bypl(size_t plane) const
{
auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc));
if (!vf)
return 0;
if (vf->bypl)
return vf->bypl(plane, size_t(this->d->m_width));
return VideoFormatGlobals::align32(size_t(this->d->m_width) * vf->bpp) / 8;
}
size_t AkVCam::VideoFormat::size() const
{
auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc));
if (!vf)
return 0;
if (vf->planeOffset)
return vf->planeOffset(vf->planes,
size_t(this->d->m_width),
size_t(this->d->m_height));
return size_t(this->d->m_height)
* VideoFormatGlobals::align32(size_t(this->d->m_width)
* vf->bpp) / 8;
}
size_t AkVCam::VideoFormat::planes() const
{
auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc));
return vf? vf->planes: 0;
}
size_t AkVCam::VideoFormat::offset(size_t plane) const
{
auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc));
if (!vf)
return 0;
if (vf->planeOffset)
return vf->planeOffset(plane,
size_t(this->d->m_width),
size_t(this->d->m_height));
return 0;
}
size_t AkVCam::VideoFormat::planeSize(size_t plane) const
{
return size_t(this->d->m_height) * this->bypl(plane);
}
bool AkVCam::VideoFormat::isValid() const
{
if (this->size() < 1)
return false;
if (this->d->m_frameRates.empty())
return false;
for (auto &fps: this->d->m_frameRates)
if (fps.num() < 1 || fps.den() < 1)
return false;
return true;
}
void AkVCam::VideoFormat::clear()
{
this->d->m_fourcc = 0;
this->d->m_width = 0;
this->d->m_height = 0;
this->d->m_frameRates.clear();
}
AkVCam::VideoFormat AkVCam::VideoFormat::nearest(const std::vector<VideoFormat> &formats) const
{
VideoFormat nearestFormat;
auto q = std::numeric_limits<uint64_t>::max();
auto svf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc));
for (auto &format: formats) {
auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(format.d->m_fourcc));
uint64_t diffFourcc = format.d->m_fourcc == this->d->m_fourcc? 0: 1;
auto diffWidth = format.d->m_width - this->d->m_width;
auto diffHeight = format.d->m_height - this->d->m_height;
auto diffBpp = vf->bpp - svf->bpp;
auto diffPlanes = vf->planes - svf->planes;
uint64_t k = diffFourcc
+ uint64_t(diffWidth * diffWidth)
+ uint64_t(diffHeight * diffHeight)
+ diffBpp * diffBpp
+ diffPlanes * diffPlanes;
if (k < q) {
nearestFormat = format;
q = k;
}
}
return nearestFormat;
}
void AkVCam::VideoFormat::roundNearest(int width, int height,
int *owidth, int *oheight,
int align)
{
/* Explanation:
*
* When 'align' is a power of 2, the left most bit will be 1 (the pivot),
* while all other bits be 0, if destination width is multiple of 'align'
* all bits after pivot position will be 0, then we create a mask
* substracting 1 to the align, so all bits after pivot position in the
* mask will 1.
* Then we negate all bits in the mask so all bits from pivot to the left
* will be 1, and then we use that mask to get a width multiple of align.
* This give us the lower (floor) width nearest to the original 'width' and
* multiple of align. To get the rounded nearest value we add align / 2 to
* 'width'.
* This is the equivalent of:
*
* align * round(width / align)
*/
*owidth = (width + (align >> 1)) & ~(align - 1);
/* Find the nearest width:
*
* round(height * owidth / width)
*/
*oheight = (2 * height * *owidth + width) / (2 * width);
}
AkVCam::FourCC AkVCam::VideoFormat::fourccFromString(const std::string &fourccStr)
{
auto vf = VideoFormatGlobals::byStr(fourccStr);
return vf? vf->format: 0;
}
std::string AkVCam::VideoFormat::stringFromFourcc(AkVCam::FourCC fourcc)
{
auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(fourcc));
return vf? vf->str: std::string();
}
AkVCam::VideoFormatPrivate::VideoFormatPrivate(FourCC fourcc,
int width,
int height,
const std::vector<Fraction> &frameRates):
m_fourcc(fourcc),
m_width(width),
m_height(height),
m_frameRates(frameRates)
{
}
const std::vector<AkVCam::VideoFormatGlobals> &AkVCam::VideoFormatGlobals::formats()
{
static const std::vector<VideoFormatGlobals> formats {
{PixelFormatRGB32, 32, 1, nullptr, nullptr, "RGB32"},
{PixelFormatRGB24, 24, 1, nullptr, nullptr, "RGB24"},
{PixelFormatRGB16, 16, 1, nullptr, nullptr, "RGB16"},
{PixelFormatRGB15, 16, 1, nullptr, nullptr, "RGB15"},
{PixelFormatBGR32, 32, 1, nullptr, nullptr, "BGR32"},
{PixelFormatBGR24, 24, 1, nullptr, nullptr, "BGR24"},
{PixelFormatBGR16, 16, 1, nullptr, nullptr, "BGR16"},
{PixelFormatBGR15, 16, 1, nullptr, nullptr, "BGR15"},
{PixelFormatUYVY , 16, 1, nullptr, nullptr, "UYVY"},
{PixelFormatYUY2 , 16, 1, nullptr, nullptr, "YUY2"},
{PixelFormatNV12 , 12, 2, offsetNV, byplNV, "NV12"},
{PixelFormatNV21 , 12, 2, offsetNV, byplNV, "NV21"}
};
return formats;
}
const AkVCam::VideoFormatGlobals *AkVCam::VideoFormatGlobals::byPixelFormat(PixelFormat pixelFormat)
{
for (auto &format: formats())
if (format.format == pixelFormat)
return &format;
return nullptr;
}
const AkVCam::VideoFormatGlobals *AkVCam::VideoFormatGlobals::byStr(const std::string &str)
{
for (auto &format: formats())
if (format.str == str)
return &format;
return nullptr;
}
size_t AkVCam::VideoFormatGlobals::offsetNV(size_t plane, size_t width, size_t height)
{
size_t offset[] = {
0,
align32(size_t(width)) * height,
5 * align32(size_t(width)) * height / 4
};
return offset[plane];
}
size_t AkVCam::VideoFormatGlobals::byplNV(size_t plane, size_t width)
{
UNUSED(plane);
return align32(size_t(width));
}
std::ostream &operator <<(std::ostream &os, const AkVCam::VideoFormat &format)
{
auto formatStr = AkVCam::VideoFormat::stringFromFourcc(format.fourcc());
os << "VideoFormat("
<< formatStr
<< ' '
<< format.width()
<< 'x'
<< format.height()
<< ' '
<< format.minimumFrameRate()
<< ')';
return os;
}
std::ostream &operator <<(std::ostream &os, const AkVCam::VideoFormats &formats)
{
bool writeComma = false;
os << "VideoFormats(";
for (auto &format: formats) {
if (writeComma)
os << ", ";
os << format;
writeComma = true;
}
os << ')';
return os;
}