Skip to content

Commit

Permalink
Merge pull request #133 from Muny/main
Browse files Browse the repository at this point in the history
@Muny Sorry later thanks! I merged temporary and will create new branch for remaining works.
  • Loading branch information
kekyo authored Apr 10, 2024
2 parents 478da89 + fd07f3f commit 1e60f4f
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 66 deletions.
228 changes: 170 additions & 58 deletions FlashCap.Core/Internal/BitmapTranscoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ internal static class BitmapTranscoder
{
private static readonly int scatteringBase = Environment.ProcessorCount;

struct ConversionConstants
{
public int multY, multUB, multUG, multVG, multVR, offsetY;
}

// Prefered article: https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#420-formats-16-bits-per-pixel

// some interesting code references:
Expand All @@ -25,11 +30,11 @@ internal static class BitmapTranscoder
// TranscodeFormats WITH FullRange suffix means that we suppose Y U and V are in [0..255] range
private static unsafe void TranscodeFromYUVInternal(
int width, int height,
TranscodeFormats conversionStandard, bool isUYVY,
TranscodeFormats conversionStandard, NativeMethods.Compression compression,
byte* pFrom, byte* pTo)
{
// constants for color conversion
int multY, multUB, multUG, multVG, multVR, offsetY;
ConversionConstants conversionConstants;

// select constants for the color conversion
switch (conversionStandard)
Expand All @@ -52,21 +57,28 @@ private static unsafe void TranscodeFromYUVInternal(

// multiply Y by 1.16438
// multiply UV by 1.13839
multY = 298;
multUB = 587;
multUG = 114;
multVG = 237;
multVR = 466;
offsetY = 16;
conversionConstants = new ConversionConstants
{
multY = 298,
multUB = 587,
multUG = 114,
multVG = 237,
multVR = 466,
offsetY = 16
};
break;

case TranscodeFormats.BT601FullRange:
multY = 255;
multUB = 516;
multUG = 100;
multVG = 208;
multVR = 409;
offsetY = 0;

conversionConstants = new ConversionConstants
{
multY = 255,
multUB = 516,
multUG = 100,
multVG = 208,
multVR = 409,
offsetY = 0
};
break;

//////////////////////////////////////////////////
Expand All @@ -87,21 +99,28 @@ private static unsafe void TranscodeFromYUVInternal(

// multiply Y by 1.16438
// multiply UV by 1.13839
multY = 298;
multUB = 541;
multUG = 55;
multVG = 137;
multVR = 459;
offsetY = 16;
conversionConstants = new ConversionConstants
{
multY = 298,
multUB = 541,
multUG = 55,
multVG = 137,
multVR = 459,
offsetY = 16
};
break;

case TranscodeFormats.BT709FullRange:
multY = 255;
multUB = 475;
multUG = 48;
multVG = 120;
multVR = 403;
offsetY = 0;

conversionConstants = new ConversionConstants
{
multY = 255,
multUB = 475,
multUG = 48,
multVG = 120,
multVR = 403,
offsetY = 0
};
break;

//////////////////////////////////////////////////
Expand All @@ -113,21 +132,29 @@ private static unsafe void TranscodeFromYUVInternal(

// multiply Y by 1.16438
// multiply UV by 1.13839
multY = 298;
multUB = 549;
multUG = 48;
multVG = 166;
multVR = 429;
offsetY = 16;

conversionConstants = new ConversionConstants
{
multY = 298,
multUB = 549,
multUG = 48,
multVG = 166,
multVR = 429,
offsetY = 16
};
break;

case TranscodeFormats.BT2020FullRange:
multY = 255;
multUB = 482;
multUG = 42;
multVG = 146;
multVR = 377;
offsetY = 0;

conversionConstants = new ConversionConstants
{
multY = 255,
multUB = 482,
multUG = 42,
multVG = 146,
multVR = 377,
offsetY = 0
};
break;

//////////////////////////////////////////////////
Expand All @@ -136,6 +163,88 @@ private static unsafe void TranscodeFromYUVInternal(
throw new ArgumentException(nameof(conversionStandard));
}

switch(compression)
{
case NativeMethods.Compression.UYVY:
case NativeMethods.Compression.HDYC:
YUY2_UYVY_to_RGB24(true, width, height, conversionConstants, compression, pFrom, pTo);
break;
case NativeMethods.Compression.YUYV:
case NativeMethods.Compression.YUY2:
YUY2_UYVY_to_RGB24(false, width, height, conversionConstants, compression, pFrom, pTo);
break;
case NativeMethods.Compression.NV12:
NV12_to_RGB24(width, height, conversionConstants, pFrom, pTo);
break;
default:
throw new ArgumentException(nameof(compression));
}
}

private static unsafe void NV12_to_RGB24(
int width, int height,
ConversionConstants cconsts,
byte* pFrom, byte* pTo)
{
var scatter = height / scatteringBase;
Parallel.For(0, (height + scatter - 1) / scatter, ys =>
{
var y = ys * scatter;
var myi = Math.Min(height - y, scatter);
for (var yi = 0; yi < myi; yi++)
{
byte* pFromY = pFrom + (y + yi) * width;
byte* pFromUV = pFrom + (height + (y + yi) / 2) * width;
byte* pToBase = pTo + (height - (y + yi) - 1) * width * 3;
for (var x = 0; x < width - 1; x += 2)
{
int c1 = pFromY[0] - cconsts.offsetY; // Y1
int c2 = pFromY[1] - cconsts.offsetY; // Y2
int d = pFromUV[0] - 128; // U
int e = pFromUV[1] - 128; // V
int cc1 = cconsts.multY * c1;
int cc2 = cconsts.multY * c2;
*pToBase++ = Clip((cc1 + cconsts.multUB * d + 128) >> 8); // B1
*pToBase++ = Clip((cc1 - cconsts.multUG * d - cconsts.multVG * e + 128) >> 8); // G1
*pToBase++ = Clip((cc1 + cconsts.multVR * e + 128) >> 8); // R1
*pToBase++ = Clip((cc2 + cconsts.multUB * d + 128) >> 8); // B2
*pToBase++ = Clip((cc2 - cconsts.multUG * d - cconsts.multVG * e + 128) >> 8); // G2
*pToBase++ = Clip((cc2 + cconsts.multVR * e + 128) >> 8); // R2
pFromY += 2;
pFromUV += 2;
}
if ((width & 1) != 0)
{
int c1 = pFromY[0] - cconsts.offsetY; // Y1
int d = pFromUV[0] - 128; // U
int e = pFromUV[1] - 128; // V
int cc1 = cconsts.multY * c1;
*pToBase++ = Clip((cc1 + cconsts.multUB * d + 128) >> 8); // B1
*pToBase++ = Clip((cc1 - cconsts.multUG * d - cconsts.multVG * e + 128) >> 8); // G1
*pToBase++ = Clip((cc1 + cconsts.multVR * e + 128) >> 8); // R1
}
}
});
}

private static unsafe void YUY2_UYVY_to_RGB24(
bool isUYVY,
int width, int height,
ConversionConstants cconsts,
NativeMethods.Compression compression,
byte* pFrom, byte* pTo)
{
var scatter = height / scatteringBase;
Parallel.For(0, (height + scatter - 1) / scatter, ys =>
{
Expand All @@ -150,31 +259,33 @@ private static unsafe void TranscodeFromYUVInternal(
for (var x = 0; x < width; x += 2)
{
// UYVY, HDYC
if (isUYVY)
{
d = pFromBase[0] - 128; // U
c1 = pFromBase[1] - offsetY; // Y1
c1 = pFromBase[1] - cconsts.offsetY; // Y1
e = pFromBase[2] - 128; // V
c2 = pFromBase[3] - offsetY; // Y2
c2 = pFromBase[3] - cconsts.offsetY; // Y2
}
// YUY2, YUYV
else
{
c1 = pFromBase[0] - offsetY; // Y1
c1 = pFromBase[0] - cconsts.offsetY; // Y1
d = pFromBase[1] - 128; // U
c2 = pFromBase[2] - offsetY; // Y2
c2 = pFromBase[2] - cconsts.offsetY; // Y2
e = pFromBase[3] - 128; // V
}
cc1 = multY * c1;
cc2 = multY * c2;
cc1 = cconsts.multY * c1;
cc2 = cconsts.multY * c2;
*pToBase++ = Clip((cc1 + multUB * d + 128) >> 8); // B1
*pToBase++ = Clip((cc1 - multUG * d - multVG * e + 128) >> 8); // G1
*pToBase++ = Clip((cc1 + multVR * e + 128) >> 8); // R1
*pToBase++ = Clip((cc1 + cconsts.multUB * d + 128) >> 8); // B1
*pToBase++ = Clip((cc1 - cconsts.multUG * d - cconsts.multVG * e + 128) >> 8); // G1
*pToBase++ = Clip((cc1 + cconsts.multVR * e + 128) >> 8); // R1
*pToBase++ = Clip((cc2 + multUB * d + 128) >> 8); // B2
*pToBase++ = Clip((cc2 - multUG * d - multVG * e + 128) >> 8); // G2
*pToBase++ = Clip((cc2 + multVR * e + 128) >> 8); // R2
*pToBase++ = Clip((cc2 + cconsts.multUB * d + 128) >> 8); // B2
*pToBase++ = Clip((cc2 - cconsts.multUG * d - cconsts.multVG * e + 128) >> 8); // G2
*pToBase++ = Clip((cc2 + cconsts.multVR * e + 128) >> 8); // R2
pFromBase += 4;
}
Expand All @@ -184,7 +295,8 @@ private static unsafe void TranscodeFromYUVInternal(

private static unsafe void TranscodeFromYUV(
int width, int height,
TranscodeFormats transcodeFormat, bool isUYVY,
TranscodeFormats transcodeFormat,
NativeMethods.Compression compression,
byte* pFrom, byte* pTo)
{
switch (transcodeFormat)
Expand All @@ -195,16 +307,16 @@ private static unsafe void TranscodeFromYUV(
case TranscodeFormats.BT709FullRange:
case TranscodeFormats.BT2020:
case TranscodeFormats.BT2020FullRange:
TranscodeFromYUVInternal(width, height, transcodeFormat, isUYVY, pFrom, pTo);
TranscodeFromYUVInternal(width, height, transcodeFormat, compression, pFrom, pTo);
break;
case TranscodeFormats.Auto:
// determine the color conversion based on the width and height of the frame
if (width > 1920 || height > 1080) // UHD or larger
TranscodeFromYUVInternal(width, height, TranscodeFormats.BT2020, isUYVY, pFrom, pTo);
TranscodeFromYUVInternal(width, height, TranscodeFormats.BT2020, compression, pFrom, pTo);
else if (width > 720 || height > 576) // HD
TranscodeFromYUVInternal(width, height, TranscodeFormats.BT709, isUYVY, pFrom, pTo);
TranscodeFromYUVInternal(width, height, TranscodeFormats.BT709, compression, pFrom, pTo);
else // SD
TranscodeFromYUVInternal(width, height, TranscodeFormats.BT601, isUYVY, pFrom, pTo);
TranscodeFromYUVInternal(width, height, TranscodeFormats.BT601, compression, pFrom, pTo);
break;
default:
throw new ArgumentException(nameof(transcodeFormat));
Expand All @@ -228,6 +340,7 @@ private static byte Clip(int value) =>
case NativeMethods.Compression.YUYV:
case NativeMethods.Compression.YUY2:
case NativeMethods.Compression.HDYC:
case NativeMethods.Compression.NV12:
return width * height * 3;
default:
return null;
Expand All @@ -244,11 +357,10 @@ public static unsafe void Transcode(
{
case NativeMethods.Compression.UYVY:
case NativeMethods.Compression.HDYC:
TranscodeFromYUV(width, height, transcodeFormat, true, pFrom, pTo);
break;
case NativeMethods.Compression.YUYV:
case NativeMethods.Compression.YUY2:
TranscodeFromYUV(width, height, transcodeFormat, false, pFrom, pTo);
case NativeMethods.Compression.NV12:
TranscodeFromYUV(width, height, transcodeFormat, compression, pFrom, pTo);
break;
default:
throw new ArgumentException(nameof(compression));
Expand Down
8 changes: 7 additions & 1 deletion FlashCap.Core/Internal/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ public enum Compression
YUYV = 0x56595559, // FOURCC
UYVY = 0x59565955, // FOURCC
MJPG = 0x47504A4D, // FOURCC
HDYC = 0x43594448 // FOURCC (BlackMagic input (UYVY))
HDYC = 0x43594448, // FOURCC (BlackMagic input (UYVY))
NV12 = 0x3231564E, // FOURCC
}

private static int CalculateClrUsed(
Expand Down Expand Up @@ -506,6 +507,7 @@ static PixelFormats GetRGBPixelFormat(int clrBits) =>
Compression.YUYV => PixelFormats.YUYV,
Compression.YUY2 => PixelFormats.YUYV,
Compression.HDYC => PixelFormats.YUYV,
Compression.NV12 => PixelFormats.NV12,
_ => PixelFormats.Unknown,
};

Expand Down Expand Up @@ -579,6 +581,10 @@ public static bool GetCompressionAndBitCount(
compression = Compression.YUYV;
bitCount = 16;
return true;
case PixelFormats.NV12:
compression = Compression.NV12;
bitCount = 12;
return true;
default:
compression = default;
bitCount = 0;
Expand Down
3 changes: 3 additions & 0 deletions FlashCap.Core/Internal/NativeMethods_V4L2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ static NativeMethods_V4L2()
pixelFormats.Add(Interop.V4L2_PIX_FMT_UYVY, PixelFormats.UYVY);
pixelFormats.Add(Interop.V4L2_PIX_FMT_YUYV, PixelFormats.YUYV);
pixelFormats.Add(Interop.V4L2_PIX_FMT_YUY2, PixelFormats.YUYV);
pixelFormats.Add(Interop.V4L2_PIX_FMT_NV12, PixelFormats.NV12);
}

public static bool IsKnownPixelFormat(uint pix_fmt) =>
Expand Down Expand Up @@ -324,6 +325,8 @@ public static uint[] GetPixelFormats(
return new[] { Interop.V4L2_PIX_FMT_UYVY };
case PixelFormats.YUYV:
return new[] { Interop.V4L2_PIX_FMT_YUYV, Interop.V4L2_PIX_FMT_YUY2 };
case PixelFormats.NV12:
return new[] { Interop.V4L2_PIX_FMT_NV12 };
case PixelFormats.JPEG:
return new[] { Interop.V4L2_PIX_FMT_MJPEG, Interop.V4L2_PIX_FMT_JPEG, (uint)NativeMethods.Compression.BI_JPEG };
case PixelFormats.PNG:
Expand Down
Loading

0 comments on commit 1e60f4f

Please sign in to comment.