diff --git a/Bundle/Linux/shaders b/Bundle/Linux/shaders
index 6c668e8..f7f0c5e 100644
Binary files a/Bundle/Linux/shaders and b/Bundle/Linux/shaders differ
diff --git a/Bundle/OSX/shaders b/Bundle/OSX/shaders
index 6aeaeaa..e2a8e85 100644
Binary files a/Bundle/OSX/shaders and b/Bundle/OSX/shaders differ
diff --git a/Bundle/Win64/shaders b/Bundle/Win64/shaders
index 6503a4f..4b90720 100644
Binary files a/Bundle/Win64/shaders and b/Bundle/Win64/shaders differ
diff --git a/Util/FFmpeg/FFmpegNativeDecoder.cs b/Util/FFmpeg/FFmpegNativeDecoder.cs
index 46737b8..d85038c 100644
--- a/Util/FFmpeg/FFmpegNativeDecoder.cs
+++ b/Util/FFmpeg/FFmpegNativeDecoder.cs
@@ -206,6 +206,12 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable
private int _srcWidth;
private int _srcHeight;
+ /// YUV→RGB matrix id (SWS_CS_*) applied to the context. 0 when no matrix is set (non-RGBA output).
+ private int _srcSwsColorspace;
+
+ /// Whether the source range applied to the context is the full range.
+ private bool _srcFullRange;
+
// ──────────────────────────────────────────────────────────────
// Public Properties
// ──────────────────────────────────────────────────────────────
@@ -296,6 +302,59 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable
}
}
+ ///
+ /// Color matrix id used to convert the YUV frames of this stream to RGB, selected from the colorspace tag of the stream.
+ /// The ids are shared with the ColorMatrix uniform of ColorConversion.compute:
+ /// 0 = BT.709 (also used when the stream has no usable tag), 1 = BT.601, 2 = BT.2020 (non-constant luminance).
+ ///
+ public int ColorMatrix
+ {
+ get
+ {
+ lock (_lock)
+ {
+ if (_pCodecContext != null && _pCodecContext->colorspace != AVColorSpace.AVCOL_SPC_UNSPECIFIED)
+ {
+ return GetColorMatrixId(_pCodecContext->colorspace);
+ }
+ if (_pFormatContext != null && _videoStreamIndex >= 0)
+ {
+ return GetColorMatrixId(_pFormatContext->streams[_videoStreamIndex]->codecpar->color_space);
+ }
+ return ColorMatrixBT709;
+ }
+ }
+ }
+
+ /// Color matrix id of BT.709.
+ // ReSharper disable InconsistentNaming
+ public const int ColorMatrixBT709 = 0;
+
+ /// Color matrix id of BT.601 (SMPTE 170M / BT.470 BG).
+ public const int ColorMatrixBT601 = 1;
+
+ /// Color matrix id of BT.2020 (non-constant luminance).
+ public const int ColorMatrixBT2020 = 2;
+ // ReSharper restore InconsistentNaming
+
+ ///
+ /// Converts a colorspace tag to a color matrix id (see ).
+ /// Tags that have no matrix of their own (unspecified, RGB, other standards) use BT.709.
+ ///
+ private static int GetColorMatrixId(AVColorSpace colorspace)
+ {
+ switch (colorspace)
+ {
+ case AVColorSpace.AVCOL_SPC_SMPTE170M:
+ case AVColorSpace.AVCOL_SPC_BT470BG:
+ return ColorMatrixBT601;
+ case AVColorSpace.AVCOL_SPC_BT2020_NCL:
+ return ColorMatrixBT2020;
+ default:
+ return ColorMatrixBT709;
+ }
+ }
+
///
/// Gets a value indicating whether hardware-accelerated decoding is currently active.
///
@@ -778,12 +837,67 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable
}
///
- /// Lazily initializes or updates the when source/destination formats or dimensions change.
+ /// Selects the YUV→RGB conversion matrix (libswscale SWS_CS_*) from the colorspace tag of a frame.
+ /// The matrix set is the same as that of the compute shader path (ColorConversion.compute, ColorMatrix uniform):
+ /// BT.709 (also for frames without a usable tag), BT.601 and BT.2020, so both paths give the same colors.
+ /// Reference: https://ffmpeg.org/doxygen/trunk/group__libsws.html (sws_getCoefficients)
+ ///
+ private static int GetSwsColorspace(AVColorSpace colorspace)
+ {
+ switch (GetColorMatrixId(colorspace))
+ {
+ case ColorMatrixBT601:
+ return ffmpeg.SWS_CS_ITU601;
+ case ColorMatrixBT2020:
+ return ffmpeg.SWS_CS_BT2020;
+ default:
+ return ffmpeg.SWS_CS_ITU709;
+ }
+ }
+
+ ///
+ /// Returns true when the Y/U/V samples of the frame use the full range (0-255) instead of the limited range (16-235 / 16-240).
+ /// The range is given by the frame's color_range tag or by a deprecated full range pixel format (YUVJ*).
+ ///
+ private static bool IsFullRange(AVFrame* frame)
+ {
+ if (frame->color_range == AVColorRange.AVCOL_RANGE_JPEG) return true;
+
+ switch ((AVPixelFormat)frame->format)
+ {
+ case AVPixelFormat.AV_PIX_FMT_YUVJ420P:
+ case AVPixelFormat.AV_PIX_FMT_YUVJ422P:
+ case AVPixelFormat.AV_PIX_FMT_YUVJ444P:
+ case AVPixelFormat.AV_PIX_FMT_YUVJ440P:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Lazily initializes or updates the when source/destination formats, dimensions,
+ /// or (for RGBA output) the colorspace / range of the source frame change.
+ /// For RGBA output the YUV→RGB matrix and the source range are taken from .
/// Reference: https://ffmpeg.org/doxygen/trunk/group__libsws.html
///
- private void InitOrUpdateSwsContext(AVPixelFormat srcFormat, int srcWidth, int srcHeight, AVPixelFormat dstFormat)
+ /// Pixel format of the source frame.
+ /// Width of the source frame in pixels.
+ /// Height of the source frame in pixels.
+ /// Pixel format of the output.
+ /// Source frame whose colorspace / range tags are applied. Required for RGBA output.
+ private void InitOrUpdateSwsContext(AVPixelFormat srcFormat, int srcWidth, int srcHeight, AVPixelFormat dstFormat, AVFrame* colorFrame = null)
{
- if (_pSwsContext != null && _srcPixFmt == srcFormat && _srcWidth == srcWidth && _srcHeight == srcHeight && _dstPixFmt == dstFormat)
+ var swsColorspace = 0;
+ var fullRange = false;
+ if (colorFrame != null && dstFormat == AVPixelFormat.AV_PIX_FMT_RGBA)
+ {
+ swsColorspace = GetSwsColorspace(colorFrame->colorspace);
+ fullRange = IsFullRange(colorFrame);
+ }
+
+ if (_pSwsContext != null && _srcPixFmt == srcFormat && _srcWidth == srcWidth && _srcHeight == srcHeight && _dstPixFmt == dstFormat
+ && _srcSwsColorspace == swsColorspace && _srcFullRange == fullRange)
{
return;
}
@@ -798,6 +912,8 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable
_srcWidth = srcWidth;
_srcHeight = srcHeight;
_dstPixFmt = dstFormat;
+ _srcSwsColorspace = swsColorspace;
+ _srcFullRange = fullRange;
_pSwsContext = ffmpeg.sws_getContext(
srcWidth, srcHeight, srcFormat,
@@ -809,6 +925,44 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable
{
throw new Exception($"Failed to initialize SwsContext (src: {srcFormat} {srcWidth}x{srcHeight} -> dst: {dstFormat} {Width}x{Height}).");
}
+
+ if (swsColorspace != 0)
+ {
+ ApplySwsColorspace(swsColorspace, fullRange);
+ }
+ }
+
+ ///
+ /// Sets the YUV→RGB matrix and the source range of .
+ /// The destination range, brightness, contrast and saturation keep the values of the context.
+ /// Reference: https://ffmpeg.org/doxygen/trunk/group__libsws.html (sws_setColorspaceDetails)
+ ///
+ /// Matrix id (SWS_CS_*) of the source.
+ /// True when the source uses the full range.
+ private void ApplySwsColorspace(int swsColorspace, bool fullRange)
+ {
+ int* currentInvTable;
+ int* currentTable;
+ int srcRange, dstRange, brightness, contrast, saturation;
+
+ // Returns a negative value when the context cannot change its colorspace details (e.g. YUV to YUV conversion).
+ if (ffmpeg.sws_getColorspaceDetails(_pSwsContext, ¤tInvTable, &srcRange, ¤tTable, &dstRange,
+ &brightness, &contrast, &saturation) < 0)
+ {
+ return;
+ }
+
+ var coefficients = ffmpeg.sws_getCoefficients(swsColorspace);
+ var invTable = new int_array4();
+ var table = new int_array4();
+ for (uint i = 0; i < 4; i++)
+ {
+ invTable[i] = coefficients[i];
+ table[i] = currentTable[i];
+ }
+
+ ffmpeg.sws_setColorspaceDetails(_pSwsContext, invTable, fullRange ? 1 : 0, table, dstRange,
+ brightness, contrast, saturation);
}
///
@@ -900,7 +1054,7 @@ internal sealed unsafe class FFmpegNativeDecoder : IDisposable
if (frame == null) return false;
timestamp = GetTimestampAndUpdateIndex(frame);
- InitOrUpdateSwsContext((AVPixelFormat)frame->format, frame->width, frame->height, AVPixelFormat.AV_PIX_FMT_RGBA);
+ InitOrUpdateSwsContext((AVPixelFormat)frame->format, frame->width, frame->height, AVPixelFormat.AV_PIX_FMT_RGBA, frame);
ClearConvertedFramePointers();
if (vflip)
diff --git a/Util/VideoBGPlayer.cs b/Util/VideoBGPlayer.cs
index 7df95e3..c27b8b4 100644
--- a/Util/VideoBGPlayer.cs
+++ b/Util/VideoBGPlayer.cs
@@ -20,6 +20,7 @@ internal class VideoBGPlayer : MonoBehaviour
private static readonly int UTex = Shader.PropertyToID("_UTex");
private static readonly int VTex = Shader.PropertyToID("_VTex");
private static readonly int ColorRange = Shader.PropertyToID("ColorRange");
+ private static readonly int ColorMatrix = Shader.PropertyToID("ColorMatrix");
[SuppressMessage("ReSharper", "InconsistentNaming")]
private enum GPUMode
@@ -307,6 +308,7 @@ internal class VideoBGPlayer : MonoBehaviour
_computeShader.SetInt(Width, w);
_computeShader.SetInt(Height, h);
_computeShader.SetInt(ColorRange, _decoder.ColorRange == AVColorRange.AVCOL_RANGE_JPEG ? 1 : 0);
+ _computeShader.SetInt(ColorMatrix, _decoder.ColorMatrix);
int threadGroupsX = (w + 7) / 8;
int threadGroupsY = (h + 7) / 8;
@@ -341,6 +343,7 @@ internal class VideoBGPlayer : MonoBehaviour
_computeShader.SetInt(Width, w);
_computeShader.SetInt(Height, h);
_computeShader.SetInt(ColorRange, _decoder.ColorRange == AVColorRange.AVCOL_RANGE_JPEG ? 1 : 0);
+ _computeShader.SetInt(ColorMatrix, _decoder.ColorMatrix);
int threadGroupsX = (w + 7) / 8;
int threadGroupsY = (h + 7) / 8;