Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions YUViewLib/src/handler/ItemMemoryHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#pragma once

#include <QString>
#include <optional>

namespace itemMemoryHandler
{
Expand Down
73 changes: 69 additions & 4 deletions YUViewLib/src/video/yuv/PixelFormatYUV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,7 @@ std::string formatSubsamplingWithColons(const Subsampling &subsampling)
PixelFormatYUV::PixelFormatYUV(const std::string_view name)
{
if (auto predefinedFormat = PredefinedPixelFormatMapper.getValue(name))
{
if (*predefinedFormat == PredefinedPixelFormat::V210)
this->predefinedPixelFormat = predefinedFormat;
}
this->predefinedPixelFormat = predefinedFormat;

std::regex strExpr(
"([YUVA]{3,6}(?:\\(IL\\))?) (4:[4210]{1}:[4210]{1}) ([0-9]{1,2})-bit[ ]?([BL]{1}E)?[ "
Expand Down Expand Up @@ -307,7 +304,26 @@ bool PixelFormatYUV::isValid() const
bool PixelFormatYUV::canConvertToRGB(Size imageSize, std::string *whyNot) const
{
if (this->predefinedPixelFormat.has_value())
{
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV15 ||
*this->predefinedPixelFormat == PredefinedPixelFormat::NV20)
{
if (imageSize.width % 2 != 0)
{
if (whyNot)
whyNot->append("The item width must be divisible by 2 for this format.\n");
return false;
}
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV15 &&
imageSize.height % 2 != 0)
{
if (whyNot)
whyNot->append("The item height must be divisible by 2 for this format.\n");
return false;
}
}
return true;
}
if (!this->isValid())
{
if (whyNot)
Expand Down Expand Up @@ -379,6 +395,33 @@ int64_t PixelFormatYUV::bytesPerFrame(const Size &frameSize) const
auto roundedUpWidth = (((frameSize.width + 48 - 1) / 48) * 48);
return frameSize.height * roundedUpWidth * 16 / 6;
}

// The semi planar NV15/NV20/NV30 formats:
// A Y plane followed by an interleaved Cb/Cr plane.
// 4 x 10 bit samples are packed into 5 bytes (little endian 40 bit word,
// the first sample in the lowest bits). The packing restarts at every line.
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV15 ||
*this->predefinedPixelFormat == PredefinedPixelFormat::NV20 ||
*this->predefinedPixelFormat == PredefinedPixelFormat::NV30)
{
const auto bytesPerLine = [](unsigned nrSamples) {
// 4 samples per 5 bytes, rounded up per line
return ((nrSamples + 3) / 4) * 5;
};
const auto w = frameSize.width;
const auto h = frameSize.height;

int64_t bytes = int64_t(h) * bytesPerLine(w); // The Y plane
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV15)
// Chroma: w/2 U and w/2 V samples per line, h/2 lines
bytes += int64_t(h / 2) * bytesPerLine(w);
else if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV20)
// Chroma: w/2 U and w/2 V samples per line, h lines
bytes += int64_t(h) * bytesPerLine(w);
else // NV30: Chroma: w U and w V samples per line, h lines
bytes += int64_t(h) * bytesPerLine(2 * w);
return bytes;
}
return -1;
}

Expand Down Expand Up @@ -502,6 +545,11 @@ unsigned PixelFormatYUV::getNrPlanes() const
{
if (*this->predefinedPixelFormat == PredefinedPixelFormat::V210)
return 3;
// The semi planar NV 10bit formats have a Y plane and an interleaved UV plane
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV15 ||
*this->predefinedPixelFormat == PredefinedPixelFormat::NV20 ||
*this->predefinedPixelFormat == PredefinedPixelFormat::NV30)
return 2;
return 0;
}

Expand All @@ -519,6 +567,12 @@ Subsampling PixelFormatYUV::getSubsampling() const
{
if (*this->predefinedPixelFormat == PredefinedPixelFormat::V210)
return Subsampling::YUV_422;
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV15)
return Subsampling::YUV_420;
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV20)
return Subsampling::YUV_422;
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV30)
return Subsampling::YUV_444;
return Subsampling::UNKNOWN;
}

Expand Down Expand Up @@ -570,6 +624,10 @@ unsigned PixelFormatYUV::getBitsPerSample() const
{
if (*this->predefinedPixelFormat == PredefinedPixelFormat::V210)
return 10;
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV15 ||
*this->predefinedPixelFormat == PredefinedPixelFormat::NV20 ||
*this->predefinedPixelFormat == PredefinedPixelFormat::NV30)
return 10;
return 0;
}

Expand Down Expand Up @@ -622,6 +680,8 @@ Offset PixelFormatYUV::getChromaOffset() const
{
if (*this->predefinedPixelFormat == PredefinedPixelFormat::V210)
return Offset({0, 0});
else if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV15)
return Offset({0, 1}); // 420
return Offset({0, 0});
}

Expand All @@ -634,6 +694,11 @@ bool PixelFormatYUV::isBytePacking() const
{
if (*this->predefinedPixelFormat == PredefinedPixelFormat::V210)
return true;
// The NV 10bit formats pack 4 x 10 bit samples into 5 bytes
if (*this->predefinedPixelFormat == PredefinedPixelFormat::NV15 ||
*this->predefinedPixelFormat == PredefinedPixelFormat::NV20 ||
*this->predefinedPixelFormat == PredefinedPixelFormat::NV30)
return true;
return false;
}

Expand Down
16 changes: 13 additions & 3 deletions YUViewLib/src/video/yuv/PixelFormatYUV.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,21 @@ enum class PredefinedPixelFormat
{
// https://developer.apple.com/library/archive/technotes/tn2162/_index.html#//apple_ref/doc/uid/DTS40013070-CH1-TNTAG8-V210__4_2_2_COMPRESSION_TYPE
// Packed 422 format with 12 10 bit values in 16 bytes
V210
V210,
// https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h
// 2 plane YCbCr
// index 0 = Y plane, [39:0] Y3:Y2:Y1:Y0 little endian
// index 1 = Cr:Cb plane, [39:0] Cr1:Cb1:Cr0:Cb0 little endian
NV15, // 420
NV20, // 422
NV30, // 444
};

constexpr EnumMapper<PredefinedPixelFormat, 1> PredefinedPixelFormatMapper = {
std::make_pair(PredefinedPixelFormat::V210, "V210")};
constexpr EnumMapper<PredefinedPixelFormat, 4> PredefinedPixelFormatMapper = {
std::make_pair(PredefinedPixelFormat::V210, "V210"),
std::make_pair(PredefinedPixelFormat::NV15, "NV15"),
std::make_pair(PredefinedPixelFormat::NV20, "NV20"),
std::make_pair(PredefinedPixelFormat::NV30, "NV30")};

enum class PackingOrder
{
Expand Down
131 changes: 130 additions & 1 deletion YUViewLib/src/video/yuv/videoHandlerYUV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,123 @@ std::pair<bool, PixelFormatYUV> convertV210PackedToPlanar(const QByteArray &sour
return {true, newFormat};
}

// Get the 10 bit sample with the given index from a line of NV15/NV20/NV30 data.
// 4 x 10 bit samples are packed into 5 bytes: A 40 bit little endian word
// containing the samples like [39:0] Y3:Y2:Y1:Y0 (the first sample is in the lowest 10 bits).
// The packing restarts at the beginning of every line. The chroma plane is interleaved
// (Cr1:Cb1:Cr0:Cb0 per group, so in sample order: Cb0, Cr0, Cb1, Cr1).
inline unsigned short getNV10bitLineSample(const unsigned char *restrict line, unsigned sampleIdx)
{
line += (sampleIdx / 4) * 5;
switch (sampleIdx % 4)
{
case 0:
return line[0] + ((line[1] & 0x03) << 8);
case 1:
return ((line[1] >> 2) & 0x3f) + ((line[2] & 0x0f) << 6);
case 2:
return (line[2] >> 4) + ((line[3] & 0x3f) << 4);
default:
return ((line[3] >> 6) & 0x03) + (line[4] << 2);
}
}


inline unsigned getNV10bitBytesPerLine(unsigned nrSamples)
{
// 4 samples per 5 bytes, rounded up per line
return ((nrSamples + 3) / 4) * 5;
}

// Convert the semi planar packed NV15 (420), NV20 (422) or NV30 (444) format to a planar format
// with 2 bytes per sample so that the normal planar conversion functions can be used.
// The NV formats consist of a Y plane followed by an interleaved Cb/Cr plane. 4 10 bit samples
// are packed into 5 bytes (per line, see getNV10bitLineSample).
std::pair<bool, PixelFormatYUV> convertNV10bitSemiPlanarPackedToPlanar(const QByteArray &sourceBuffer,
QByteArray &targetBuffer,
const Size curFrameSize,
const PredefinedPixelFormat nvFormat)
{
Subsampling subsampling;
if (nvFormat == PredefinedPixelFormat::NV15)
subsampling = Subsampling::YUV_420;
else if (nvFormat == PredefinedPixelFormat::NV20)
subsampling = Subsampling::YUV_422;
else
subsampling = Subsampling::YUV_444;

const auto w = curFrameSize.width;
const auto h = curFrameSize.height;

// The output format is planar 10 bit with the same subsampling
auto newFormat = PixelFormatYUV(subsampling, 10, PlaneOrder::YUV);
const auto bytesPerOutFrame = newFormat.bytesPerFrame(curFrameSize);
if (targetBuffer.size() < bytesPerOutFrame)
targetBuffer.resize(bytesPerOutFrame);

// The interleaved chroma plane contains one U and one V sample per chroma position.
const auto chromaWidth = (subsampling == Subsampling::YUV_444) ? w : w / 2;
const auto chromaHeight = (subsampling == Subsampling::YUV_420) ? h / 2 : h;
const auto strideY = getNV10bitBytesPerLine(w);
const auto strideC = getNV10bitBytesPerLine(chromaWidth * 2);

const unsigned char *restrict srcY = (unsigned char *)sourceBuffer.data();
const unsigned char *restrict srcC = srcY + strideY * h;
unsigned short *restrict dstY = (unsigned short *)targetBuffer.data();
unsigned short *restrict dstU = dstY + w * h;
unsigned short *restrict dstV = dstU + chromaWidth * chromaHeight;

for (unsigned y = 0; y < h; y++)
{
const unsigned char *restrict lineY = srcY + y * strideY;
unsigned short *restrict outY = dstY + y * w;
for (unsigned x = 0; x < w; x++)
outY[x] = getNV10bitLineSample(lineY, x);
}

for (unsigned y = 0; y < chromaHeight; y++)
{
const unsigned char *restrict lineC = srcC + y * strideC;
unsigned short *restrict outU = dstU + y * chromaWidth;
unsigned short *restrict outV = dstV + y * chromaWidth;
for (unsigned x = 0; x < chromaWidth; x++)
{
outU[x] = getNV10bitLineSample(lineC, x * 2);
outV[x] = getNV10bitLineSample(lineC, x * 2 + 1);
}
}

return {true, newFormat};
}

yuv_t getPixelValueNV10bit(const QByteArray &sourceBuffer,
const Size &curFrameSize,
const QPoint &pixelPos,
const PredefinedPixelFormat nvFormat)
{
const auto w = curFrameSize.width;
const auto h = curFrameSize.height;

const auto chromaWidth = (nvFormat == PredefinedPixelFormat::NV30) ? w : w / 2;
const auto subHor = (nvFormat == PredefinedPixelFormat::NV30) ? 1u : 2u;
const auto subVer = (nvFormat == PredefinedPixelFormat::NV15) ? 2u : 1u;
const auto strideY = getNV10bitBytesPerLine(w);
const auto strideC = getNV10bitBytesPerLine(chromaWidth * 2);

const unsigned char *restrict srcY = (unsigned char *)sourceBuffer.data();
const unsigned char *restrict srcC = srcY + strideY * h;

yuv_t ret;
ret.Y = getNV10bitLineSample(srcY + pixelPos.y() * strideY, pixelPos.x());

const auto chromaLine = srcC + (pixelPos.y() / subVer) * strideC;
const unsigned chromaIndex = (unsigned(pixelPos.x()) / subHor) * 2;
ret.U = getNV10bitLineSample(chromaLine, chromaIndex);
ret.V = getNV10bitLineSample(chromaLine, chromaIndex + 1);

return ret;
}

yuv_t getPixelValueV210(const QByteArray &sourceBuffer,
const Size &curFrameSize,
const QPoint &pixelPos)
Expand Down Expand Up @@ -2346,6 +2463,11 @@ void convertYUVToImage(const QByteArray &sourceBuffer,
if (*predefinedFormat == PredefinedPixelFormat::V210)
std::tie(convOK, newPixelFormat) =
convertV210PackedToPlanar(sourceBuffer, tmpPlanarYUVSource, curFrameSize);
else if (*predefinedFormat == PredefinedPixelFormat::NV15 ||
*predefinedFormat == PredefinedPixelFormat::NV20 ||
*predefinedFormat == PredefinedPixelFormat::NV30)
std::tie(convOK, newPixelFormat) = convertNV10bitSemiPlanarPackedToPlanar(
sourceBuffer, tmpPlanarYUVSource, curFrameSize, *predefinedFormat);
else
convOK = false;
}
Expand Down Expand Up @@ -2380,7 +2502,10 @@ std::vector<PixelFormatYUV> videoHandlerYUV::formatPresetList = {
PixelFormatYUV(Subsampling::YUV_420, 10, PlaneOrder::YUV),
PixelFormatYUV(Subsampling::YUV_422, 8, PlaneOrder::YUV),
PixelFormatYUV(Subsampling::YUV_444, 8, PlaneOrder::YUV),
PixelFormatYUV(PredefinedPixelFormat::V210)};
PixelFormatYUV(PredefinedPixelFormat::V210),
PixelFormatYUV(PredefinedPixelFormat::NV15),
PixelFormatYUV(PredefinedPixelFormat::NV20),
PixelFormatYUV(PredefinedPixelFormat::NV30)};

videoHandlerYUV::videoHandlerYUV() : videoHandler()
{
Expand Down Expand Up @@ -3288,6 +3413,10 @@ yuv_t videoHandlerYUV::getPixelValue(const QPoint &pixelPos) const
{
if (predefinedFormat == PredefinedPixelFormat::V210)
value = getPixelValueV210(currentFrameRawData, frameSize, pixelPos);
else if (*predefinedFormat == PredefinedPixelFormat::NV15 ||
*predefinedFormat == PredefinedPixelFormat::NV20 ||
*predefinedFormat == PredefinedPixelFormat::NV30)
value = getPixelValueNV10bit(currentFrameRawData, frameSize, pixelPos, *predefinedFormat);
}
else if (format.isPlanar())
{
Expand Down