MythTV master
mythvideocolourspace.cpp
Go to the documentation of this file.
1// MythTV
5#include "mythavutil.h"
8
9// libavutil
10extern "C" {
11#include "libavutil/pixfmt.h"
12#include "libavutil/pixdesc.h"
13}
14
15// Std
16#include <cmath>
17
18#define LOC QString("ColourSpace: ")
19
41 : ReferenceCounter("Colour"),
42 m_updatesDisabled(false)
43{
48 m_dbSettings[kPictureAttribute_Range] = static_cast<int>(gCoreContext->GetBoolSetting("GUIRGBLevels", true));
49 m_primariesMode = toPrimariesMode(gCoreContext->GetSetting("ColourPrimariesMode", "auto"));
50
56
57 // This isn't working as intended (most notable on OSX internal display).
58 // Presumably the driver is expecting sRGB/Rec709 and handles any final
59 // conversion to the display's colourspace.
60 /*
61 if (HasMythMainWindow())
62 {
63 MythDisplay* display = MythDisplay::AcquireRelease();
64 MythEDID& edid = display->GetEDID();
65 // We assume sRGB/Rec709 by default
66 bool custom = edid.Valid() && !edid.IsSRGB();
67 bool likesrgb = custom && edid.IsLikeSRGB() && m_primariesMode != PrimariesExact;
68 if (custom)
69 {
70 // Use sRGB if we don't want exact matching (i.e. close is good enough)
71 // and the display primaries are similar to sRGB.
72 if (likesrgb && qFuzzyCompare(edid.Gamma() + 1.0F, 2.2F + 1.0F))
73 {
74 LOG(VB_PLAYBACK, LOG_INFO, LOC + "sRGB primaries preferred as close match to display primaries");
75 }
76 else
77 {
78 m_customDisplayGamma = edid.Gamma();
79 m_customDisplayPrimaries = new ColourPrimaries;
80 MythEDID::Primaries displayprimaries = edid.ColourPrimaries();
81 memcpy(m_customDisplayPrimaries, &displayprimaries, sizeof(ColourPrimaries));
82 }
83 }
84 MythDisplay::AcquireRelease(false);
85 }
86 */
87 Update();
88}
89
91{
93}
94
96{
99}
100
102{
104}
105
112{
113 if (Supported == m_supportedAttributes)
114 return;
115 m_supportedAttributes = Supported;
116 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("PictureAttributes: %1").arg(toString(m_supportedAttributes)));
118}
119
121{
122 if (auto found = m_dbSettings.find(Attribute); found != m_dbSettings.end())
123 return found->second;
124 return -1;
125}
126
134{
136 return;
137
138 // build an RGB to YCbCr conversion matrix from first principles
139 // and then invert it for the YCbCr to RGB conversion
140 std::vector<float> rgb;
141 switch (static_cast<AVColorSpace>(m_colourSpace))
142 {
143 case AVCOL_SPC_RGB: rgb = { 1.0000F, 1.0000F, 1.0000F }; break;
144 case AVCOL_SPC_BT709: rgb = { 0.2126F, 0.7152F, 0.0722F }; break;
145 case AVCOL_SPC_FCC: rgb = { 0.30F, 0.59F, 0.11F }; break;
146 case AVCOL_SPC_BT470BG:
147 case AVCOL_SPC_SMPTE170M: rgb = { 0.299F, 0.587F, 0.114F }; break;
148 case AVCOL_SPC_SMPTE240M: rgb = { 0.212F, 0.701F, 0.087F }; break;
149 case AVCOL_SPC_YCOCG: rgb = { 0.25F, 0.5F, 0.25F }; break;
150 case AVCOL_SPC_BT2020_CL:
151 case AVCOL_SPC_BT2020_NCL: rgb = { 0.2627F, 0.6780F, 0.0593F }; break;
152 case AVCOL_SPC_UNSPECIFIED:
153 case AVCOL_SPC_RESERVED:
154 case AVCOL_SPC_SMPTE2085:
155 case AVCOL_SPC_CHROMA_DERIVED_CL:
156 case AVCOL_SPC_CHROMA_DERIVED_NCL:
157 case AVCOL_SPC_ICTCP:
158 default: rgb = { 0.2126F, 0.7152F, 0.0722F }; //Rec.709
159 }
160
161 float bs = rgb[2] == 1.0F ? 0.0F : 0.5F / (rgb[2] - 1.0F);
162 float rs = rgb[0] == 1.0F ? 0.0F : 0.5F / (rgb[0] - 1.0F);
163 QMatrix4x4 rgb2yuv( rgb[0], rgb[1], rgb[2], 0.0F,
164 bs * rgb[0], bs * rgb[1], 0.5F, 0.0F,
165 0.5F, rs * rgb[1], rs * rgb[2], 0.0F,
166 0.0F, 0.0F, 0.0F, m_alpha);
167
168 // TODO check AVCOL_SPC_RGB
169 if (m_colourSpace == AVCOL_SPC_YCOCG)
170 {
171 rgb2yuv = QMatrix4x4(0.25F, 0.50F, 0.25F, 0.0F,
172 -0.25F, 0.50F, -0.25F, 0.0F,
173 0.50F, 0.00F, -0.50F, 0.0F,
174 0.00F, 0.00F, 0.00F, m_alpha);
175 }
176 QMatrix4x4 yuv2rgb = rgb2yuv.inverted();
177
178 // scale the chroma values for saturation
179 yuv2rgb.scale(1.0F, m_saturation, m_saturation);
180 // rotate the chroma for hue - this is a rotation around the 'Y' (luminance) axis
181 yuv2rgb.rotate(m_hue, 1.0F, 0.0F, 0.0F);
182 // denormalise chroma
183 yuv2rgb.translate(0.0F, -0.5F, -0.5F);
184 // Levels adjustment
185 // This is a no-op when using full range MJPEG sources and full range output
186 // or 'standard' limited range MPEG sources with limited range output.
187 // N.B all of the quantization parameters scale perfectly between the different
188 // standards and bitdepths (i.e. 709 8 and 10 bit, 2020 10 and 12bit).
189 // In the event that we are displaying a downsampled format, the following
190 // also effectively limits the precision in line with that loss in precision.
191 // For example, YUV420P10 is downsampled by removing the 2 lower bits of
192 // precision. We identify the resultant YUV420P frame as 8bit and calculate
193 // the quantization/range accordingly.
194 bool expand = (m_range == AVCOL_RANGE_MPEG) && m_fullRange;
195 bool contract = (m_range == AVCOL_RANGE_JPEG) && !m_fullRange;
196 bool noop = !expand && !contract;
197 float depth = (1 << m_colourSpaceDepth) - 1;
198 float blacklevel = 16 << (m_colourSpaceDepth - 8);
199 float lumapeak = 235 << (m_colourSpaceDepth - 8);
200 float chromapeak = 240 << (m_colourSpaceDepth - 8);
201 float luma_scale {NAN};
202 float chroma_scale {NAN};
203 float offset {NAN};
204 if (noop)
205 {
206 luma_scale = 1.0F;
207 chroma_scale = 1.0F;
208 offset = 0.0F;
209 }
210 else if (expand)
211 {
212 luma_scale = depth / (lumapeak - blacklevel);
213 chroma_scale = depth / (chromapeak - blacklevel);
214 offset = -blacklevel / depth;
215 } else {
216 luma_scale = (lumapeak - blacklevel) / depth;
217 chroma_scale = (chromapeak - blacklevel) / depth;
218 offset = blacklevel / depth;
219 }
220
221 setToIdentity();
223 scale(m_contrast);
224 this->operator *= (yuv2rgb);
225 scale(luma_scale, chroma_scale, chroma_scale);
226 translate(offset, offset, offset);
227
228 // Scale when needed for 10/12/16bit fixed point data
229 // Raw 10bit video is represented as XXXXXXXX:XX000000
230 // Raw 12bit video is XXXXXXXX:XXXX0000
231 // Raw 16bit video is XXXXXXXX:XXXXXXXX
232 // and these formats are returned by FFmpeg when software decoding
233 // so we need to shift by the appropriate number of 'bits' (actually a float in the shader)
234 // Hardware decoders seem to return 'corrected' values
235 // i.e. 10bit as XXXXXXXXXX for both direct rendering and copy back.
236 // Works for NVDEC and VAAPI. VideoToolBox untested.
238 {
239 float scaler = 65535.0F / ((1 << m_colourSpaceDepth) -1);
240 scale(scaler);
241 }
242 static_cast<QMatrix4x4*>(this)->operator = (this->transposed());
243
244 // check for a change in primaries conversion. This will need a recompile
245 // of the shaders - not just a parameter update.
246 float tmpsrcgamma = m_colourGamma;
247 float tmpdspgamma = m_displayGamma;
248 QMatrix4x4 tmpmatrix = m_primaryMatrix;
250 bool primchanged = !qFuzzyCompare(tmpsrcgamma, m_colourGamma) ||
251 !qFuzzyCompare(tmpdspgamma, m_displayGamma) ||
252 !qFuzzyCompare(tmpmatrix, m_primaryMatrix);
253 Debug();
254 emit Updated(primchanged);
255}
256
258{
259 bool primary = !m_primaryMatrix.isIdentity();
260
261 LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
262 QString("Brightness: %1 Contrast: %2 Saturation: %3 Hue: %4 Alpha: %5 Range: %6 Primary: %7")
263 .arg(static_cast<qreal>(m_brightness), 2, 'f', 4, QLatin1Char('0'))
264 .arg(static_cast<qreal>(m_contrast) , 2, 'f', 4, QLatin1Char('0'))
265 .arg(static_cast<qreal>(m_saturation), 2, 'f', 4, QLatin1Char('0'))
266 .arg(static_cast<qreal>(m_hue) , 2, 'f', 4, QLatin1Char('0'))
267 .arg(static_cast<qreal>(m_alpha) , 2, 'f', 4, QLatin1Char('0'))
268 .arg(m_fullRange ? "Full" : "Limited")
269 .arg(primary));
270
271 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_DEBUG))
272 {
273 QString stream;
274 QDebug debug(&stream);
275 debug << *this;
276 if (primary)
278 LOG(VB_PLAYBACK, LOG_DEBUG, stream);
279 }
280}
281
282int MythVideoColourSpace::ChangePictureAttribute(PictureAttribute Attribute, bool Direction, int Value)
283{
284 if (!(m_supportedAttributes & toMask(Attribute)))
285 return -1;
286
287 int current = GetPictureAttribute(Attribute);
288 if (current < 0)
289 return -1;
290
291 int newvalue = Value;
292 if (Value < 0)
293 {
294 newvalue = current + ((Direction) ? +1 : -1);
295 if (kPictureAttribute_Hue == Attribute)
296 newvalue = newvalue % 100;
297 if ((kPictureAttribute_Range == Attribute) && newvalue > 1)
298 newvalue = 1;
299 }
300
301 newvalue = std::clamp(newvalue, 0, 100);
302 if (newvalue != current)
303 {
304 switch (Attribute)
305 {
307 SetBrightness(newvalue);
308 break;
310 SetContrast(newvalue);
311 break;
313 SetSaturation(newvalue);
314 break;
316 SetHue(newvalue);
317 break;
318 default:
319 newvalue = -1;
320 }
321
322 emit PictureAttributeChanged(Attribute, newvalue);
323
324 if (newvalue >= 0)
325 SaveValue(Attribute, newvalue);
326 }
327
328 return newvalue;
329}
330
338{
339 if (!Frame)
340 return false;
341
342 int csp = Frame->m_colorspace;
343 int primary = Frame->m_colorprimaries;
344 int transfer = Frame->m_colortransfer;
345 int chroma = Frame->m_chromalocation;
346 int raw = csp;
347 int rawchroma = chroma;
348 VideoFrameType frametype = Frame->m_type;
349 VideoFrameType softwaretype = MythAVUtil::PixelFormatToFrameType(static_cast<AVPixelFormat>(Frame->m_swPixFmt));
350
351 // workaround for NVDEC. NVDEC defaults to a colorspace of 0 - which happens
352 // to equate to RGB. In testing, NVDEC reports the same colourspace as FFmpeg
353 // software decode for MPEG2, MPEG4, H.264, HEVC and VP8. VP9 seems to go wrong (with limited samples)
354 bool forced = false;
355 if (csp == AVCOL_SPC_RGB && (MythVideoFrame::YUVFormat(frametype) || frametype == FMT_NVDEC))
356 {
357 forced = true;
358 csp = AVCOL_SPC_UNSPECIFIED;
359 }
360 int range = Frame->m_colorrange;
361 if (range == AVCOL_RANGE_UNSPECIFIED)
362 range = AVCOL_RANGE_MPEG;
363 int depth = MythVideoFrame::ColorDepth(MythVideoFrame::HardwareFormat(frametype) ? softwaretype : frametype);
364 if (csp == AVCOL_SPC_UNSPECIFIED)
365 csp = (Frame->m_width < 1280) ? AVCOL_SPC_BT470BG : AVCOL_SPC_BT709;
366 if (primary == AVCOL_PRI_UNSPECIFIED)
367 primary = (Frame->m_width < 1280) ? AVCOL_PRI_BT470BG : AVCOL_PRI_BT709;
368 if (transfer == AVCOL_TRC_UNSPECIFIED)
369 transfer = (Frame->m_width < 1280) ? AVCOL_TRC_GAMMA28 : AVCOL_TRC_BT709;
370 if (chroma == AVCHROMA_LOC_UNSPECIFIED)
371 chroma = AVCHROMA_LOC_LEFT;
372
373 if ((csp == m_colourSpace) && (m_colourSpaceDepth == depth) &&
374 (m_range == range) && (m_colourShifted == Frame->m_colorshifted) &&
375 (primary == m_colourPrimaries) && (chroma == m_chromaLocation))
376 {
377 return false;
378 }
379
380 m_colourSpace = csp;
381 m_colourSpaceDepth = depth;
382 m_range = range;
383 m_colourShifted = Frame->m_colorshifted;
384 m_colourPrimaries = primary;
385 m_colourTransfer = transfer;
386 m_chromaLocation = chroma;
387
388 if (forced)
389 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Forcing inconsistent colourspace - frame format %1")
391
392 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Input : %1(%2) Depth:%3 %4Range:%5")
393 .arg(av_color_space_name(static_cast<AVColorSpace>(m_colourSpace)),
394 m_colourSpace == raw ? "Detected" : "Guessed",
395 QString::number(m_colourSpaceDepth),
396 (m_colourSpaceDepth > 8) ? (m_colourShifted ? "(Pre-scaled) " : "(Fixed point) ") : "",
397 (AVCOL_RANGE_JPEG == m_range) ? "Full" : "Limited"));
398 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Input : Primaries:%1 Transfer: %2")
399 .arg(av_color_primaries_name(static_cast<AVColorPrimaries>(m_colourPrimaries)),
400 av_color_transfer_name(static_cast<AVColorTransferCharacteristic>(m_colourTransfer))));
401 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Output: Range:%1 Primaries: %2")
402 .arg(m_fullRange ? "Full" : "Limited",
403 m_customDisplayPrimaries ? "Custom (screen)" :
404 av_color_primaries_name(static_cast<AVColorPrimaries>(m_displayPrimaries))));
405 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Chroma location: %1 %2")
406 .arg(av_chroma_location_name(static_cast<AVChromaLocation>(m_chromaLocation)),
407 rawchroma == m_chromaLocation ? "(Detected)" : "(Guessed)"));
408
409 Update();
410
411 if (!m_primaryMatrix.isIdentity())
412 {
413 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Enabled colourspace primaries conversion from %1 to %2")
414 .arg(av_color_primaries_name(static_cast<AVColorPrimaries>(m_colourPrimaries)),
416 ? "Custom (screen)"
417 : av_color_primaries_name(static_cast<AVColorPrimaries>(m_displayPrimaries))));
418 }
419 return true;
420}
421
423{
424 m_fullRange = FullRange;
425 Update();
426}
427
429{
430 m_brightness = (Value * 0.02F) - 1.0F;
431 Update();
432}
433
435{
436 m_contrast = Value * 0.02F;
437 Update();
438}
439
441{
442 m_hue = Value * -3.6F;
443 Update();
444}
445
447{
448 m_saturation = Value * 0.02F;
449 Update();
450}
451
453{
454 m_alpha = 100.0F / Value;
455 Update();
456}
457
459{
460 QStringList result;
461
462 // TODO extend this to handle all non-co-sited chroma locations. Left is the
463 // most common by far - otherwise topleft seems to be used by some 422 content (but
464 // subjectively looks better without adjusting vertically!)
465 if (m_chromaLocation == AVCHROMA_LOC_LEFT || m_chromaLocation == AVCHROMA_LOC_TOPLEFT ||
466 m_chromaLocation == AVCHROMA_LOC_BOTTOMLEFT)
467 {
468 result << "CHROMALEFT";
469 }
470
471 if (!m_primaryMatrix.isIdentity())
472 result << "COLOURMAPPING";
473 return result;
474}
475
477{
478 return m_primaryMatrix;
479}
480
482{
483 return m_colourGamma;
484}
485
487{
488 return m_displayGamma;
489}
490
492{
493 return m_primariesMode;
494}
495
497{
498 return m_range;
499}
500
502{
503 return m_colourSpace;
504}
505
507{
509 Update();
510}
511
514{
515 QString dbName;
516 if (kPictureAttribute_Brightness == AttributeType)
517 dbName = "PlaybackBrightness";
518 else if (kPictureAttribute_Contrast == AttributeType)
519 dbName = "PlaybackContrast";
520 else if (kPictureAttribute_Colour == AttributeType)
521 dbName = "PlaybackColour";
522 else if (kPictureAttribute_Hue == AttributeType)
523 dbName = "PlaybackHue";
524
525 if (!dbName.isEmpty())
526 gCoreContext->SaveSetting(dbName, Value);
527
528 m_dbSettings[AttributeType] = Value;
529}
530
532{
533 // Default to identity
534 QMatrix4x4 result;
535
536 // User isn't interested in quality
538 return result;
539
540 auto source = static_cast<AVColorPrimaries>(Source);
541 auto dest = static_cast<AVColorPrimaries>(Dest);
542 auto custom = m_customDisplayPrimaries != nullptr;
543
544 // No-op
545 if (!custom && (source == dest))
546 return result;
547
548 auto srcprimaries = GetPrimaries(source, m_colourGamma);
549 auto dstprimaries = GetPrimaries(dest, m_displayGamma);
550 if (custom)
551 {
552 dstprimaries = *m_customDisplayPrimaries;
554 }
555
556 // If 'exact' is not requested and the primaries and gamma are similar, then
557 // ignore. Note: 0.021F should cover any differences between Rec.709/sRGB and Rec.610
558 if ((m_primariesMode == PrimariesRelaxed) && qFuzzyCompare(m_colourGamma + 1.0F, m_displayGamma + 1.0F) &&
559 MythColourSpace::Alike(srcprimaries, dstprimaries, 0.021F))
560 {
561 return result;
562 }
563
564 return (MythColourSpace::RGBtoXYZ(srcprimaries) *
565 MythColourSpace::RGBtoXYZ(dstprimaries).inverted());
566}
567
569{
570 auto primary = static_cast<AVColorPrimaries>(Primary);
571 Gamma = 2.2F;
572 switch (primary)
573 {
574 case AVCOL_PRI_BT470BG:
575 case AVCOL_PRI_BT470M: return MythColourSpace::s_BT610_625;
576 case AVCOL_PRI_SMPTE170M:
577 case AVCOL_PRI_SMPTE240M: return MythColourSpace::s_BT610_525;
578 case AVCOL_PRI_BT2020: Gamma = 2.4F; return MythColourSpace::s_BT2020;
579 default: return MythColourSpace::s_BT709;
580 }
581}
static VideoFrameType PixelFormatToFrameType(AVPixelFormat Fmt)
Definition: mythavutil.cpp:72
static bool Alike(const MythColourSpace &First, const MythColourSpace &Second, float Fuzz)
static QMatrix4x4 RGBtoXYZ(const MythColourSpace &Primaries)
Create a conversion matrix for RGB to XYZ with the given primaries.
static MythColourSpace s_BT610_625
static MythColourSpace s_BT709
static MythColourSpace s_BT610_525
static MythColourSpace s_BT2020
void SaveSetting(const QString &key, int newValue)
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
bool GetBoolSetting(const QString &key, bool defaultval=false)
PictureAttributeSupported m_supportedAttributes
float GetDisplayGamma(void) const
void Update(void)
Update the matrix for the current settings and colourspace.
void SetPrimariesMode(PrimariesMode Mode)
void PictureAttributeChanged(PictureAttribute Attribute, int Value)
void SetSupportedAttributes(PictureAttributeSupported Supported)
Enable the given set of picture attributes.
MythColourSpace * m_customDisplayPrimaries
int GetPictureAttribute(PictureAttribute Attribute)
void Updated(bool PrimariesChanged)
std::map< PictureAttribute, int > m_dbSettings
static MythColourSpace GetPrimaries(int Primary, float &Gamma)
PictureAttributeSupported SupportedAttributes(void) const
void SupportedAttributesChanged(PictureAttributeSupported Supported)
QStringList GetColourMappingDefines(void)
QMatrix4x4 GetPrimaryMatrix(void)
void PictureAttributesUpdated(const std::map< PictureAttribute, int > &Values)
PrimariesMode GetPrimariesMode(void)
bool UpdateColourSpace(const MythVideoFrame *Frame)
Set the current colourspace to use.
float GetColourGamma(void) const
int ChangePictureAttribute(PictureAttribute Attribute, bool Direction, int Value)
void SetFullRange(bool FullRange)
QMatrix4x4 GetPrimaryConversion(int Source, int Dest)
void SaveValue(PictureAttribute Attribute, int Value)
Save the PictureAttribute value to the database.
static QString FormatDescription(VideoFrameType Type)
Definition: mythframe.cpp:368
static bool YUVFormat(VideoFrameType Type)
Definition: mythframe.h:465
static int ColorDepth(int Format)
Definition: mythframe.h:398
static bool HardwareFormat(VideoFrameType Type)
Definition: mythframe.h:424
General purpose reference counter.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
VideoFrameType
Definition: mythframe.h:20
@ FMT_NVDEC
Definition: mythframe.h:62
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
static QString Source(const QNetworkRequest &request)
Definition: netstream.cpp:139
static void yuv2rgb(int y, int cr, int cb, eu8 *r, eu8 *g, eu8 *b)
Definition: pxsup2dast.c:215
static void rgb2yuv(eu8 r, eu8 g, eu8 b, eu8 *y, eu8 *cr, eu8 *cb)
Definition: pxsup2dast.c:228
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:206
Mode
Definition: synaesthesia.h:23
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_GENERAL
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:89
PictureAttributeSupported
PrimariesMode toPrimariesMode(const QString &Mode)
PictureAttribute
@ kPictureAttribute_Range
@ kPictureAttribute_Contrast
@ kPictureAttribute_Brightness
@ kPictureAttribute_Colour
@ kPictureAttribute_Hue
PictureAttributeSupported toMask(PictureAttribute PictureAttribute)
PrimariesMode
@ PrimariesDisabled
@ PrimariesRelaxed