MythTV  master
videocolourspace.cpp
Go to the documentation of this file.
1 // MythTV
2 #include "mythcorecontext.h"
3 #include "mythlogging.h"
4 #include "mythavutil.h"
5 #include "videocolourspace.h"
6 
7 // libavutil
8 extern "C" {
9 #include "libavutil/pixfmt.h"
10 #include "libavutil/pixdesc.h"
11 }
12 
13 // Std
14 #include <cmath>
15 
17  {{{0.640F, 0.330F}, {0.300F, 0.600F}, {0.150F, 0.060F}}, {0.3127F, 0.3290F}};
19  {{{0.640F, 0.340F}, {0.310F, 0.595F}, {0.155F, 0.070F}}, {0.3127F, 0.3290F}};
21  {{{0.640F, 0.330F}, {0.290F, 0.600F}, {0.150F, 0.060F}}, {0.3127F, 0.3290F}};
23  {{{0.708F, 0.292F}, {0.170F, 0.797F}, {0.131F, 0.046F}}, {0.3127F, 0.3290F}};
24 
25 #define LOC QString("ColourSpace: ")
26 
56  : ReferenceCounter("Colour"),
57  m_parent(Parent)
58 {
59  if (m_parent)
60  {
61  m_parent->IncrRef();
70  }
71  else
72  {
78  }
79 
85  m_updatesDisabled = false;
86  Update();
87 }
88 
90 {
92  if (m_parent)
93  m_parent->DecrRef();
94 }
95 
97 {
98  return m_supportedAttributes;
99 }
100 
107 {
108  m_supportedAttributes = Supported;
109  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("PictureAttributes: %1").arg(toString(m_supportedAttributes)));
110 }
111 
113 {
114  if (m_dbSettings.contains(Attribute))
115  return m_dbSettings.value(Attribute);
116  return -1;
117 }
118 
121 {
122  if (!(m_supportedAttributes & toMask(Attribute)))
123  return -1;
124 
125  Value = std::min(std::max(Value, 0), 100);
126 
127  switch (Attribute)
128  {
130  SetBrightness(Value);
131  break;
133  SetContrast(Value);
134  break;
136  SetSaturation(Value);
137  break;
139  SetHue(Value);
140  break;
141  default:
142  Value = -1;
143  }
144 
145  emit PictureAttributeChanged(Attribute, Value);
146 
147  if (Value >= 0)
148  SaveValue(Attribute, Value);
149 
150  return Value;
151 }
152 
160 {
161  if (m_updatesDisabled)
162  return;
163 
164  // build an RGB to YCbCr conversion matrix from first principles
165  // and then invert it for the YCbCr to RGB conversion
166  std::vector<float> rgb;
167  switch (static_cast<AVColorSpace>(m_colourSpace))
168  {
169  case AVCOL_SPC_RGB: rgb = { 1.0000F, 1.0000F, 1.0000F }; break;
170  case AVCOL_SPC_BT709: rgb = { 0.2126F, 0.7152F, 0.0722F }; break;
171  case AVCOL_SPC_FCC: rgb = { 0.30F, 0.59F, 0.11F }; break;
172  case AVCOL_SPC_BT470BG:
173  case AVCOL_SPC_SMPTE170M: rgb = { 0.299F, 0.587F, 0.114F }; break;
174  case AVCOL_SPC_SMPTE240M: rgb = { 0.212F, 0.701F, 0.087F }; break;
175  case AVCOL_SPC_YCOCG: rgb = { 0.25F, 0.5F, 0.25F }; break;
176  case AVCOL_SPC_BT2020_CL:
177  case AVCOL_SPC_BT2020_NCL: rgb = { 0.2627F, 0.6780F, 0.0593F }; break;
178  case AVCOL_SPC_UNSPECIFIED:
179  case AVCOL_SPC_RESERVED:
180  case AVCOL_SPC_SMPTE2085:
181  case AVCOL_SPC_CHROMA_DERIVED_CL:
182  case AVCOL_SPC_CHROMA_DERIVED_NCL:
183  case AVCOL_SPC_ICTCP:
184  default: rgb = { 0.2126F, 0.7152F, 0.0722F }; //Rec.709
185  }
186 
187  float bs = rgb[2] == 1.0F ? 0.0F : 0.5F / (rgb[2] - 1.0F);
188  float rs = rgb[0] == 1.0F ? 0.0F : 0.5F / (rgb[0] - 1.0F);
189  QMatrix4x4 rgb2yuv( rgb[0], rgb[1], rgb[2], 0.0F,
190  bs * rgb[0], bs * rgb[1], 0.5F, 0.0F,
191  0.5F, rs * rgb[1], rs * rgb[2], 0.0F,
192  0.0F, 0.0F, 0.0F, m_alpha);
193 
194  // TODO check AVCOL_SPC_RGB
195  if (m_colourSpace == AVCOL_SPC_YCOCG)
196  {
197  rgb2yuv = QMatrix4x4(0.25F, 0.50F, 0.25F, 0.0F,
198  -0.25F, 0.50F, -0.25F, 0.0F,
199  0.50F, 0.00F, -0.50F, 0.0F,
200  0.00F, 0.00F, 0.00F, m_alpha);
201  }
202  QMatrix4x4 yuv2rgb = rgb2yuv.inverted();
203 
204  // scale the chroma values for saturation
205  yuv2rgb.scale(1.0F, m_saturation, m_saturation);
206  // rotate the chroma for hue - this is a rotation around the 'Y' (luminance) axis
207  yuv2rgb.rotate(m_hue, 1.0F, 0.0F, 0.0F);
208  // denormalise chroma
209  yuv2rgb.translate(0.0F, -0.5F, -0.5F);
210  // Levels adjustment
211  // This is a no-op when using full range MJPEG sources and full range output
212  // or 'standard' limited range MPEG sources with limited range output.
213  // N.B all of the quantization parameters scale perfectly between the different
214  // standards and bitdepths (i.e. 709 8 and 10 bit, 2020 10 and 12bit).
215  // In the event that we are displaying a downsampled format, the following
216  // also effectively limits the precision in line with that loss in precision.
217  // For example, YUV420P10 is downsampled by removing the 2 lower bits of
218  // precision. We identify the resultant YUV420P frame as 8bit and calculate
219  // the quantization/range accordingly.
220  bool expand = (m_range == AVCOL_RANGE_MPEG) && m_fullRange;
221  bool contract = (m_range == AVCOL_RANGE_JPEG) && !m_fullRange;
222  bool noop = !expand && !contract;
223  float depth = (1 << m_colourSpaceDepth) - 1;
224  float blacklevel = 16 << (m_colourSpaceDepth - 8);
225  float lumapeak = 235 << (m_colourSpaceDepth - 8);
226  float chromapeak = 240 << (m_colourSpaceDepth - 8);
227  float luma_scale = noop ? 1.0F : (expand ? depth / (lumapeak - blacklevel) : (lumapeak - blacklevel) / depth);
228  float chroma_scale = noop ? 1.0F : (expand ? depth / (chromapeak - blacklevel) : (chromapeak - blacklevel) / depth);
229  float offset = noop ? 0.0F : (expand ? -blacklevel / depth : blacklevel / depth);
230 
231  setToIdentity();
233  scale(m_contrast);
234  this->operator *= (yuv2rgb);
235  scale(luma_scale, chroma_scale, chroma_scale);
236  translate(offset, offset, offset);
237 
238  // Scale when needed for 10/12/16bit fixed point data
239  // Raw 10bit video is represented as XXXXXXXX:XX000000
240  // Raw 12bit video is XXXXXXXX:XXXX0000
241  // Raw 16bit video is XXXXXXXX:XXXXXXXX
242  // and these formats are returned by FFmpeg when software decoding
243  // so we need to shift by the appropriate number of 'bits' (actually a float in the shader)
244  // Hardware decoders seem to return 'corrected' values
245  // i.e. 10bit as XXXXXXXXXX for both direct rendering and copy back.
246  // Works for NVDEC and VAAPI. VideoToolBox untested.
247  if ((m_colourSpaceDepth > 8) && !m_colourShifted)
248  {
249  float scaler = 65535.0F / ((1 << m_colourSpaceDepth) -1);
250  scale(scaler);
251  }
252  static_cast<QMatrix4x4*>(this)->operator = (this->transposed());
253 
254  // check for a change in primaries conversion. This will need a recompile
255  // of the shaders - not just a parameter update.
256  float tmpsrcgamma = m_colourGamma;
257  float tmpdspgamma = m_displayGamma;
258  QMatrix4x4 tmpmatrix = m_primaryMatrix;
260  bool primchanged = !qFuzzyCompare(tmpsrcgamma, m_colourGamma) ||
261  !qFuzzyCompare(tmpdspgamma, m_displayGamma) ||
262  !qFuzzyCompare(tmpmatrix, m_primaryMatrix);
263  Debug();
264  emit Updated(primchanged);
265 }
266 
268 {
269  bool primary = !m_primaryMatrix.isIdentity();
270 
271  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
272  QString("Brightness: %1 Contrast: %2 Saturation: %3 Hue: %4 Alpha: %5 Range: %6 Primary: %7")
273  .arg(static_cast<qreal>(m_brightness), 2, 'f', 4, QLatin1Char('0'))
274  .arg(static_cast<qreal>(m_contrast) , 2, 'f', 4, QLatin1Char('0'))
275  .arg(static_cast<qreal>(m_saturation), 2, 'f', 4, QLatin1Char('0'))
276  .arg(static_cast<qreal>(m_hue) , 2, 'f', 4, QLatin1Char('0'))
277  .arg(static_cast<qreal>(m_alpha) , 2, 'f', 4, QLatin1Char('0'))
278  .arg(m_fullRange ? "Full" : "Limited")
279  .arg(primary));
280 
281  if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_DEBUG))
282  {
283  QString stream;
284  QDebug debug(&stream);
285  debug << *this;
286  if (primary)
288  LOG(VB_PLAYBACK, LOG_DEBUG, stream);
289  }
290 }
291 
299 {
300  if (!Frame)
301  return false;
302 
303  int csp = Frame->colorspace;
304  int primary = Frame->colorprimaries;
305  int transfer = Frame->colortransfer;
306  int raw = csp;
307  VideoFrameType frametype = Frame->codec;
308  VideoFrameType softwaretype = PixelFormatToFrameType(static_cast<AVPixelFormat>(Frame->sw_pix_fmt));
309 
310  // workaround for NVDEC. NVDEC defaults to a colorspace of 0 - which happens
311  // to equate to RGB. In testing, NVDEC reports the same colourspace as FFmpeg
312  // software decode for MPEG2, MPEG4, H.264, HEVC and VP8. VP9 seems to go wrong (with limited samples)
313  bool forced = false;
314  if (csp == AVCOL_SPC_RGB && (format_is_yuv(frametype) || frametype == FMT_NVDEC))
315  {
316  forced = true;
317  csp = AVCOL_SPC_UNSPECIFIED;
318  }
319  int range = Frame->colorrange;
320  if (range == AVCOL_RANGE_UNSPECIFIED)
321  range = AVCOL_RANGE_MPEG;
322  int depth = ColorDepth(format_is_hw(frametype) ? softwaretype : frametype);
323  if (csp == AVCOL_SPC_UNSPECIFIED)
324  csp = (Frame->width < 1280) ? AVCOL_SPC_BT470BG : AVCOL_SPC_BT709;
325  if (primary == AVCOL_PRI_UNSPECIFIED)
326  primary = (Frame->width < 1280) ? AVCOL_PRI_BT470BG : AVCOL_PRI_BT709;
327  if (transfer == AVCOL_TRC_UNSPECIFIED)
328  transfer = (Frame->width < 1280) ? AVCOL_TRC_GAMMA28 : AVCOL_TRC_BT709;
329  if ((csp == m_colourSpace) && (m_colourSpaceDepth == depth) &&
330  (m_range == range) && (m_colourShifted == Frame->colorshifted) &&
331  (primary == m_colourPrimaries))
332  {
333  return false;
334  }
335 
336  m_colourSpace = csp;
337  m_colourSpaceDepth = depth;
338  m_range = range;
339  m_colourShifted = Frame->colorshifted;
340  m_colourPrimaries = primary;
341  m_colourTransfer = transfer;
342 
343  if (forced)
344  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Forcing inconsistent colourspace - frame format %1")
345  .arg(format_description(Frame->codec)));
346 
347  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Input : %1(%2) Depth:%3 %4Range:%5")
348  .arg(av_color_space_name(static_cast<AVColorSpace>(m_colourSpace)))
349  .arg(m_colourSpace == raw ? "Detected" : "Guessed")
350  .arg(m_colourSpaceDepth)
351  .arg((m_colourSpaceDepth > 8) ? (m_colourShifted ? "(Pre-scaled) " : "(Fixed point) ") : "")
352  .arg((AVCOL_RANGE_JPEG == m_range) ? "Full" : "Limited"));
353  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Input : Primaries:%1 Transfer: %2")
354  .arg(av_color_primaries_name(static_cast<AVColorPrimaries>(m_colourPrimaries)))
355  .arg(av_color_transfer_name(static_cast<AVColorTransferCharacteristic>(m_colourTransfer))));
356  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Output: Range:%1 Primaries: %2")
357  .arg(m_fullRange ? "Full" : "Limited")
358  .arg(m_customDisplayPrimaries ? "Custom (screen)" :
359  av_color_primaries_name(static_cast<AVColorPrimaries>(m_displayPrimaries))));
360 
361  Update();
362 
363  if (!m_primaryMatrix.isIdentity())
364  {
365  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Enabled colourspace primaries conversion from %1 to %2")
366  .arg(av_color_primaries_name(static_cast<AVColorPrimaries>(m_colourPrimaries)))
367  .arg(m_customDisplayPrimaries ? "Custom (screen)" :
368  av_color_primaries_name(static_cast<AVColorPrimaries>(m_displayPrimaries))));
369  }
370  return true;
371 }
372 
373 void VideoColourSpace::SetFullRange(bool FullRange)
374 {
375  m_fullRange = FullRange;
376  Update();
377 }
378 
380 {
381  m_brightness = (Value * 0.02F) - 1.0F;
382  Update();
383 }
384 
386 {
387  m_contrast = Value * 0.02F;
388  Update();
389 }
390 
392 {
393  m_hue = Value * -3.6F;
394  Update();
395 }
396 
398 {
399  m_saturation = Value * 0.02F;
400  Update();
401 }
402 
404 {
405  m_alpha = 100.0F / Value;
406  Update();
407 }
408 
410 {
411  QStringList result;
412  if (m_primaryMatrix.isIdentity())
413  return result;
414 
415  result << "COLOURMAPPING";
416  return result;
417 }
418 
420 {
421  return m_primaryMatrix;
422 }
423 
425 {
426  return m_colourGamma;
427 }
428 
430 {
431  return m_displayGamma;
432 }
433 
435 {
436  return m_primariesMode;
437 }
438 
440 {
441  m_primariesMode = Mode;
442  Update();
443 }
444 
446 void VideoColourSpace::SaveValue(PictureAttribute AttributeType, int Value)
447 {
448  // parent owns the database settings
449  if (m_parent)
450  return;
451 
452  QString dbName;
453  if (kPictureAttribute_Brightness == AttributeType)
454  dbName = "PlaybackBrightness";
455  else if (kPictureAttribute_Contrast == AttributeType)
456  dbName = "PlaybackContrast";
457  else if (kPictureAttribute_Colour == AttributeType)
458  dbName = "PlaybackColour";
459  else if (kPictureAttribute_Hue == AttributeType)
460  dbName = "PlaybackHue";
461 
462  if (!dbName.isEmpty())
463  gCoreContext->SaveSetting(dbName, Value);
464 
465  m_dbSettings[AttributeType] = Value;
466 }
467 
469 {
470  QMatrix4x4 result; // identity
471  auto source = static_cast<AVColorPrimaries>(Source);
472  auto dest = static_cast<AVColorPrimaries>(Dest);
473 
474  if ((source == dest) || (m_primariesMode == PrimariesDisabled))
475  return result;
476 
477  ColourPrimaries srcprimaries, dstprimaries;
478  GetPrimaries(source, srcprimaries, m_colourGamma);
479  GetPrimaries(dest, dstprimaries, m_displayGamma);
480 
481  // Auto will only enable if there is a significant difference between source
482  // and destination. Most people will not notice the difference bt709 and bt610 etc
483  // and we avoid extra GPU processing.
484  // BT2020 is currently the main target - which is easily differentiated by its gamma.
485  if ((m_primariesMode == PrimariesAuto) && qFuzzyCompare(m_colourGamma + 1.0F, m_displayGamma + 1.0F))
486  return result;
487 
488  // N.B. Custom primaries are not yet implemented but will, some day soon,
489  // be read from the EDID
490  if (m_customDisplayPrimaries != nullptr)
491  {
492  dstprimaries = *m_customDisplayPrimaries;
494  }
495 
496  return (RGBtoXYZ(srcprimaries) * RGBtoXYZ(dstprimaries).inverted());
497 }
498 
499 void VideoColourSpace::GetPrimaries(int Primary, ColourPrimaries &Out, float &Gamma)
500 {
501  auto primary = static_cast<AVColorPrimaries>(Primary);
502  Gamma = 2.2F;
503  switch (primary)
504  {
505  case AVCOL_PRI_BT470BG:
506  case AVCOL_PRI_BT470M: Out = BT610_625; return;
507  case AVCOL_PRI_SMPTE170M:
508  case AVCOL_PRI_SMPTE240M: Out = BT610_525; return;
509  case AVCOL_PRI_BT2020: Out = BT2020; Gamma = 2.4F; return;
510  default: Out = BT709; return;
511  }
512 }
513 
514 inline float CalcBy(const float p[3][2], const float w[2])
515 {
516  float val = ((1-w[0])/w[1] - (1-p[0][0])/p[0][1]) * (p[1][0]/p[1][1] - p[0][0]/p[0][1]) -
517  (w[0]/w[1] - p[0][0]/p[0][1]) * ((1-p[1][0])/p[1][1] - (1-p[0][0])/p[0][1]);
518  val /= ((1-p[2][0])/p[2][1] - (1-p[0][0])/p[0][1]) * (p[1][0]/p[1][1] - p[0][0]/p[0][1]) -
519  (p[2][0]/p[2][1] - p[0][0]/p[0][1]) * ((1-p[1][0])/p[1][1] - (1-p[0][0])/p[0][1]);
520  return val;
521 }
522 
523 inline float CalcGy(const float p[3][2], const float w[2], const float By)
524 {
525  float val = w[0]/w[1] - p[0][0]/p[0][1] - By * (p[2][0]/p[2][1] - p[0][0]/p[0][1]);
526  val /= p[1][0]/p[1][1] - p[0][0]/p[0][1];
527  return val;
528 }
529 
530 inline float CalcRy(const float By, const float Gy)
531 {
532  return 1.0F - Gy - By;
533 }
534 
543 {
544  float By = CalcBy(Primaries.primaries, Primaries.whitepoint);
545  float Gy = CalcGy(Primaries.primaries, Primaries.whitepoint, By);
546  float Ry = CalcRy(By, Gy);
547 
548  float temp[4][4];
549  temp[0][0] = Ry * Primaries.primaries[0][0] / Primaries.primaries[0][1];
550  temp[0][1] = Gy * Primaries.primaries[1][0] / Primaries.primaries[1][1];
551  temp[0][2] = By * Primaries.primaries[2][0] / Primaries.primaries[2][1];
552  temp[1][0] = Ry;
553  temp[1][1] = Gy;
554  temp[1][2] = By;
555  temp[2][0] = Ry / Primaries.primaries[0][1] * (1- Primaries.primaries[0][0] - Primaries.primaries[0][1]);
556  temp[2][1] = Gy / Primaries.primaries[1][1] * (1- Primaries.primaries[1][0] - Primaries.primaries[1][1]);
557  temp[2][2] = By / Primaries.primaries[2][1] * (1- Primaries.primaries[2][0] - Primaries.primaries[2][1]);
558  temp[0][3] = temp[1][3] = temp[2][3] = temp[3][0] = temp[3][1] = temp[3][2] = 0.0F;
559  temp[3][3] = 1.0F;
560  return QMatrix4x4(temp[0]);
561 }
float CalcBy(const float p[3][2], const float w[2])
PrimariesMode GetPrimariesMode(void)
float CalcGy(const float p[3][2], const float w[2], const float By)
VideoColourSpace contains a QMatrix4x4 that can convert YCbCr data to RGB.
PrimariesMode
QMap< PictureAttribute, int > m_dbSettings
float GetColourGamma(void)
void SetPrimariesMode(PrimariesMode Mode)
PictureAttributeSupported
QString toString(MarkTypes type)
void SaveSetting(const QString &key, int newValue)
General purpose reference counter.
void SetSaturation(int Value)
int GetPictureAttribute(PictureAttribute Attribute)
VideoFrameType PixelFormatToFrameType(AVPixelFormat fmt)
Definition: mythavutil.cpp:68
VideoFrameType
Definition: mythframe.h:23
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void SetFullRange(bool FullRange)
static const ColourPrimaries BT610_625
int ColorDepth(int Format)
Return the color depth for the given MythTV frame format.
Definition: mythframe.cpp:814
#define LOC
static void GetPrimaries(int Primary, ColourPrimaries &Out, float &Gamma)
void PictureAttributeChanged(PictureAttribute Attribute, int Value)
static const ColourPrimaries BT610_525
VideoColourSpace(VideoColourSpace *Parent=nullptr)
QMatrix4x4 GetPrimaryMatrix(void)
float GetDisplayGamma(void)
virtual int IncrRef(void)
Increments reference count.
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:24
static int format_is_hw(VideoFrameType Type)
Definition: mythframe.h:72
void SetAlpha(int Value)
static int format_is_yuv(VideoFrameType Type)
Definition: mythframe.h:114
QStringList GetColourMappingDefines(void)
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
static const ColourPrimaries BT2020
static void yuv2rgb(int y, int cr, int cb, eu8 *r, eu8 *g, eu8 *b)
Definition: pxsup2dast.c:202
static void rgb2yuv(eu8 r, eu8 g, eu8 b, eu8 *y, eu8 *cr, eu8 *cb)
Definition: pxsup2dast.c:215
PictureAttribute
Definition: videoouttypes.h:89
ColourPrimaries * m_customDisplayPrimaries
void SetHue(int Value)
void Updated(bool PrimariesChanged)
PictureAttributeSupported m_supportedAttributes
void SetBrightness(int Value)
void SetSupportedAttributes(PictureAttributeSupported Supported)
Enable the given set of picture attributes.
int GetNumSetting(const QString &key, int defaultval=0)
PictureAttributeSupported toMask(PictureAttribute pictureattribute)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QMatrix4x4 GetPrimaryConversion(int Source, int Dest)
bool GetBoolSetting(const QString &key, bool defaultval=false)
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_GENERAL
VideoColourSpace * m_parent
PictureAttributeSupported SupportedAttributes(void) const
void SaveValue(PictureAttribute Attribute, int Value)
Save the PictureAttribute value to the database.
const char * format_description(VideoFrameType Type)
Definition: mythframe.cpp:33
int SetPictureAttribute(PictureAttribute Attribute, int Value)
Set the Value for the given PictureAttribute.
QMatrix4x4 m_primaryMatrix
void SetContrast(int Value)
void Update(void)
Update the matrix for the current settings and colourspace.
PrimariesMode m_primariesMode
static const ColourPrimaries BT709
bool UpdateColourSpace(const VideoFrame *Frame)
Set the current colourspace to use.
static QMatrix4x4 RGBtoXYZ(ColourPrimaries Primaries)
Create a conversion matrix for RGB to XYZ with the given primaries.
float CalcRy(const float By, const float Gy)