MoD*_*oDJ 7 video ffmpeg ios metal
我已经多次阅读BT.709 规范,但不清楚的是,编码的 H.264 比特流是否应该将任何伽马曲线应用于编码数据?请注意 BT.709 规范中对类似伽马公式的具体提及。Apple 提供了从 CoreVideo 读取 YUV 数据的 OpenGL 或 Metal 着色器示例,提供的缓冲区不进行任何类型的伽马调整。YUV 值正在被读取和处理,就好像它们是简单的线性值一样。我还检查了 ffmpeg 的源代码,发现在 BT.709 缩放步骤之后没有应用伽马调整。然后我创建了一个测试视频只有两种线性灰度颜色 5 和 26 对应于 2% 和 10% 的级别。当使用 ffmpeg 和 iMovie 转换为 H.264 时,输出 BT.709 值是 (YCbCr) (20 128 128) 和 (38 128 128) 并且这些值与 BT.709 转换矩阵的输出完全匹配,没有任何伽马调整。
可以在Quicktime Gamma Bug 中找到有关此主题的大量背景资料。似乎 Quicktime 和 Adobe 编码器的一些历史问题不正确地进行不同的伽马调整,结果使视频流在不同的播放器上看起来很糟糕。这确实令人困惑,因为如果与sRGB进行比较,它清楚地表明如何应用伽马编码,然后对其进行解码以在 sRGB 和线性之间进行转换。如果在创建 h.264 数据流时在矩阵步骤之后没有应用伽马调整,为什么 BT.709 会详细介绍相同类型的伽马调整曲线?h.264 流中的所有颜色步骤是否都应编码为直线(伽马 1.0)值?
如果特定的示例输入会使事情更清楚,我附上了 3 个颜色条图像,不同颜色的确切值可以显示在带有这些图像文件的图像编辑器中。
第一个图像在 sRGB 色彩空间中,并被标记为 sRGB。
第二张图像已转换为线性 RGB 色彩空间,并使用线性 RGB 配置文件进行标记。
这个第三图像已被转换成与来自REC709-ELLE-V4-rec709.icc REC.709轮廓水平elles_icc_profiles 。这似乎是模拟 BT.709 中描述的“相机”伽马需要做的事情。
请注意右下角的 sRGB 值 (0x555555) 如何变为线性 RGB (0x171717),而 BT.709 伽马编码值变为 (0x464646)。不清楚的是我是否应该将线性 RGB 值传递到 ffmpeg 中,或者我是否应该传递一个已经 BT.709 伽马编码的值,然后需要在线性转换矩阵步骤之前在客户端解码以返回到 RGB .
更新:
根据反馈,我更新了基于 C 的实现和 Metal 着色器,并作为 iOS 示例项目MetalBT709Decoder上传到 github 。
编码归一化的线性 RGB 值是这样实现的:
static inline
int BT709_convertLinearRGBToYCbCr(
float Rn,
float Gn,
float Bn,
int *YPtr,
int *CbPtr,
int *CrPtr,
int applyGammaMap)
{
// Gamma adjustment to non-linear value
if (applyGammaMap) {
Rn = BT709_linearNormToNonLinear(Rn);
Gn = BT709_linearNormToNonLinear(Gn);
Bn = BT709_linearNormToNonLinear(Bn);
}
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf
float Ey = (Kr * Rn) + (Kg * Gn) + (Kb * Bn);
float Eb = (Bn - Ey) / Eb_minus_Ey_Range;
float Er = (Rn - Ey) / Er_minus_Ey_Range;
// Quant Y to range [16, 235] (inclusive 219 values)
// Quant Eb, Er to range [16, 240] (inclusive 224 values, centered at 128)
float AdjEy = (Ey * (YMax-YMin)) + 16;
float AdjEb = (Eb * (UVMax-UVMin)) + 128;
float AdjEr = (Er * (UVMax-UVMin)) + 128;
*YPtr = (int) round(AdjEy);
*CbPtr = (int) round(AdjEb);
*CrPtr = (int) round(AdjEr);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
从 YCbCr 到线性 RGB 的解码是这样实现的:
static inline
int BT709_convertYCbCrToLinearRGB(
int Y,
int Cb,
int Cr,
float *RPtr,
float *GPtr,
float *BPtr,
int applyGammaMap)
{
// https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.709_conversion
// http://www.niwa.nu/2013/05/understanding-yuv-values/
// Normalize Y to range [0 255]
//
// Note that the matrix multiply will adjust
// this byte normalized range to account for
// the limited range [16 235]
float Yn = (Y - 16) * (1.0f / 255.0f);
// Normalize Cb and CR with zero at 128 and range [0 255]
// Note that matrix will adjust to limited range [16 240]
float Cbn = (Cb - 128) * (1.0f / 255.0f);
float Crn = (Cr - 128) * (1.0f / 255.0f);
const float YScale = 255.0f / (YMax-YMin);
const float UVScale = 255.0f / (UVMax-UVMin);
const
float BT709Mat[] = {
YScale, 0.000f, (UVScale * Er_minus_Ey_Range),
YScale, (-1.0f * UVScale * Eb_minus_Ey_Range * Kb_over_Kg), (-1.0f * UVScale * Er_minus_Ey_Range * Kr_over_Kg),
YScale, (UVScale * Eb_minus_Ey_Range), 0.000f,
};
// Matrix multiply operation
//
// rgb = BT709Mat * YCbCr
// Convert input Y, Cb, Cr to normalized float values
float Rn = (Yn * BT709Mat[0]) + (Cbn * BT709Mat[1]) + (Crn * BT709Mat[2]);
float Gn = (Yn * BT709Mat[3]) + (Cbn * BT709Mat[4]) + (Crn * BT709Mat[5]);
float Bn = (Yn * BT709Mat[6]) + (Cbn * BT709Mat[7]) + (Crn * BT709Mat[8]);
// Saturate normalzied linear (R G B) to range [0.0, 1.0]
Rn = saturatef(Rn);
Gn = saturatef(Gn);
Bn = saturatef(Bn);
// Gamma adjustment for RGB components after matrix transform
if (applyGammaMap) {
Rn = BT709_nonLinearNormToLinear(Rn);
Gn = BT709_nonLinearNormToLinear(Gn);
Bn = BT709_nonLinearNormToLinear(Bn);
}
*RPtr = Rn;
*GPtr = Gn;
*BPtr = Bn;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我相信这个逻辑是正确实现的,但我很难验证结果。当我生成一个包含伽马调整颜色值 (osxcolor_test_image_24bit_BT709.m4v) 的 .m4v 文件时,结果会如预期的那样出现。但是我在这里找到的像 (bars_709_Frame01.m4v) 这样的测试用例似乎不起作用,因为颜色条值似乎被编码为线性(没有伽马调整)。
对于 SMPTE 测试图案,0.75 灰度级是线性 RGB (191 191 191),如果此 RGB 没有伽马调整编码为 (Y Cb Cr) (180 128 128) 或者比特流中的值是否应显示为伽马调整(Y Cb Cr) (206 128 128)?
(跟进)在对这个伽马问题进行额外研究后,很明显苹果在 AVFoundation 中实际使用的是 1.961 伽马函数。使用 AVAssetWriterInputPixelBufferAdaptor、使用 vImage 或 CoreVideo API 进行编码时就是这种情况。这个分段伽马函数定义如下:
#define APPLE_GAMMA_196 (1.960938f)
static inline
float Apple196_nonLinearNormToLinear(float normV) {
const float xIntercept = 0.05583828f;
if (normV < xIntercept) {
normV *= (1.0f / 16.0f);
} else {
const float gamma = APPLE_GAMMA_196;
normV = pow(normV, gamma);
}
return normV;
}
static inline
float Apple196_linearNormToNonLinear(float normV) {
const float yIntercept = 0.00349f;
if (normV < yIntercept) {
normV *= 16.0f;
} else {
const float gamma = 1.0f / APPLE_GAMMA_196;
normV = pow(normV, gamma);
}
return normV;
}
Run Code Online (Sandbox Code Playgroud)
您最初的问题:使用 BT.709 矩阵的 H.264 编码视频是否包含伽玛调整?
编码视频仅包含伽玛调整 - 如果您向编码器提供伽玛调整值。
H.264 编码器不关心传输特性。因此,如果您压缩线性然后解压缩 - 您将得到线性。所以如果你用伽玛压缩然后解压缩 - 你会得到伽玛。
或者如果您的位是用 Rec. 编码的。709 传递函数 - 编码器不会改变伽玛。
但您可以将 H.264 流中的传输特性指定为元数据。(ITU-T H.264 (04/2017) E.1.1 VUI 参数语法建议书)。因此,编码流携带颜色空间信息,但不用于编码或解码。
我假设 8 位视频总是包含非线性传递函数。否则你会相当不明智地使用 8 位。
如果您转换为线性来进行效果和合成 - 我建议增加位深度或线性化为浮点数。
色彩空间由原色、传递函数和矩阵系数组成。伽马调整被编码在传递函数中(而不是矩阵中)。
| 归档时间: |
|
| 查看次数: |
3227 次 |
| 最近记录: |