Skip to content

Commit

Permalink
Add support for 12 bit 420, 422 and 444 YUV video frames. Make sure t…
Browse files Browse the repository at this point in the history
…hat we can detect then properly when they come out of libvpx and that we can convert them to half-floats properly when we send them to the compositor.

BUG=445071
CQ_INCLUDE_TRYBOTS=master.tryserver.blink:linux_precise_blink_rel

Review-Url: https://codereview.chromium.org/2370453003
Cr-Commit-Position: refs/heads/master@{#421610}
  • Loading branch information
hubbe authored and Commit bot committed Sep 28, 2016
1 parent 73c79ad commit 6cf2c09
Show file tree
Hide file tree
Showing 17 changed files with 238 additions and 46 deletions.
98 changes: 66 additions & 32 deletions cc/resources/video_resource_updater.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ VideoFrameExternalResources::ResourceType ResourceTypeForVideoFrame(
case media::PIXEL_FORMAT_YUV420P10:
case media::PIXEL_FORMAT_YUV422P10:
case media::PIXEL_FORMAT_YUV444P10:
case media::PIXEL_FORMAT_YUV420P12:
case media::PIXEL_FORMAT_YUV422P12:
case media::PIXEL_FORMAT_YUV444P12:
case media::PIXEL_FORMAT_UNKNOWN:
break;
}
Expand Down Expand Up @@ -289,6 +292,25 @@ static gfx::Size SoftwarePlaneDimension(media::VideoFrame* input_frame,
return gfx::Size(plane_width, plane_height);
}

void VideoResourceUpdater::MakeHalfFloats(const uint16_t* src,
int bits_per_channel,
size_t num,
uint16_t* dst) {
// TODO(hubbe): Make AVX and neon versions of this code.

// This magic constant is 2^-112. Multiplying by this
// is the same as subtracting 112 from the exponent, which
// is the difference in exponent bias between 32-bit and
// 16-bit floats. Once we've done this subtraction, we can
// simply extract the low bits of the exponent and the high
// bits of the mantissa from our float and we're done.
float mult = 1.9259299444e-34f / ((1 << bits_per_channel) - 1);
for (size_t i = 0; i < num; i++) {
float value = src[i] * mult;
dst[i] = (*(uint32_t*)&value) >> 13;
}
}

VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes(
scoped_refptr<media::VideoFrame> video_frame) {
TRACE_EVENT0("cc", "VideoResourceUpdater::CreateForSoftwarePlanes");
Expand Down Expand Up @@ -327,6 +349,11 @@ VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes(
case media::PIXEL_FORMAT_YUV444P10:
bits_per_channel = 10;
break;
case media::PIXEL_FORMAT_YUV420P12:
case media::PIXEL_FORMAT_YUV422P12:
case media::PIXEL_FORMAT_YUV444P12:
bits_per_channel = 12;
break;
}

// Only YUV software video frames are supported.
Expand Down Expand Up @@ -482,9 +509,34 @@ VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes(
// LUMINANCE_F16 uses half-floats, so we always need a conversion step.
if (plane_resource.resource_format() == LUMINANCE_F16) {
needs_conversion = true;
// Note that the current method of converting integers to half-floats
// stops working if you have more than 10 bits of data.
DCHECK_LE(bits_per_channel, 10);

// If the input data was 9 or 10 bit, and we output to half-floats,
// then we used the OR path below, which means that we need to
// adjust the resource offset and multiplier accordingly. If the
// input data uses more than 10 bits, it will already be normalized
// to 0.0..1.0, so there is no need to do anything.
if (bits_per_channel <= 10) {
// By OR-ing with 0x3800, 10-bit numbers become half-floats in the
// range [0.5..1) and 9-bit numbers get the range [0.5..0.75).
//
// Half-floats are evaluated as:
// float value = pow(2.0, exponent - 25) * (0x400 + fraction);
//
// In our case the exponent is 14 (since we or with 0x3800) and
// pow(2.0, 14-25) * 0x400 evaluates to 0.5 (our offset) and
// pow(2.0, 14-25) * fraction is [0..0.49951171875] for 10-bit and
// [0..0.24951171875] for 9-bit.
//
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format
//
// PLEASE NOTE:
// All planes are assumed to use the same multiplier/offset.
external_resources.offset = 0.5f;
// Max value from input data.
int max_input_value = (1 << bits_per_channel) - 1;
// 2 << 11 = 2048 would be 1.0 with our exponent.
external_resources.multiplier = 2048.0 / max_input_value;
}
} else if (bits_per_channel > 8) {
// If bits_per_channel > 8 and we can't use LUMINANCE_F16, we need to
// shift the data down and create an 8-bit texture.
Expand All @@ -508,13 +560,17 @@ VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes(
&upload_pixels_[upload_image_stride * row]);
const uint16_t* src = reinterpret_cast<uint16_t*>(
video_frame->data(i) + (video_stride_bytes * row));
// Micro-benchmarking indicates that the compiler does
// a good enough job of optimizing this loop that trying
// to manually operate on one uint64 at a time is not
// actually helpful.
// Note to future optimizers: Benchmark your optimizations!
for (size_t i = 0; i < bytes_per_row / 2; i++)
dst[i] = src[i] | 0x3800;
if (bits_per_channel <= 10) {
// Micro-benchmarking indicates that the compiler does
// a good enough job of optimizing this loop that trying
// to manually operate on one uint64 at a time is not
// actually helpful.
// Note to future optimizers: Benchmark your optimizations!
for (size_t i = 0; i < bytes_per_row / 2; i++)
dst[i] = src[i] | 0x3800;
} else {
MakeHalfFloats(src, bits_per_channel, bytes_per_row / 2, dst);
}
} else if (shift != 0) {
// We have more-than-8-bit input which we need to shift
// down to fit it into an 8-bit texture.
Expand All @@ -540,28 +596,6 @@ VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes(
plane_resource.SetUniqueId(video_frame->unique_id(), i);
}

if (plane_resource.resource_format() == LUMINANCE_F16) {
// By OR-ing with 0x3800, 10-bit numbers become half-floats in the
// range [0.5..1) and 9-bit numbers get the range [0.5..0.75).
//
// Half-floats are evaluated as:
// float value = pow(2.0, exponent - 25) * (0x400 + fraction);
//
// In our case the exponent is 14 (since we or with 0x3800) and
// pow(2.0, 14-25) * 0x400 evaluates to 0.5 (our offset) and
// pow(2.0, 14-25) * fraction is [0..0.49951171875] for 10-bit and
// [0..0.24951171875] for 9-bit.
//
// (https://en.wikipedia.org/wiki/Half-precision_floating-point_format)
//
// PLEASE NOTE: This doesn't work if bits_per_channel is > 10.
// PLEASE NOTE: All planes are assumed to use the same multiplier/offset.
external_resources.offset = 0.5f;
// Max value from input data.
int max_input_value = (1 << bits_per_channel) - 1;
// 2 << 11 = 2048 would be 1.0 with our exponent.
external_resources.multiplier = 2048.0 / max_input_value;
}

// VideoResourceUpdater shares a context with the compositor so a
// sync token is not required.
Expand Down
9 changes: 9 additions & 0 deletions cc/resources/video_resource_updater.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ class CC_EXPORT VideoResourceUpdater
VideoFrameExternalResources CreateExternalResourcesFromVideoFrame(
scoped_refptr<media::VideoFrame> video_frame);

// Convert an array of short integers into an array of half-floats.
// |src| is an array of integers in range 0 .. 2^{bits_per_channel} - 1
// |num| is number of entries in input and output array.
// The numbers stored in |dst| will be half floats in range 0.0..1.0
static void MakeHalfFloats(const uint16_t* src,
int bits_per_channel,
size_t num,
uint16_t* dst);

private:
class PlaneResource {
public:
Expand Down
49 changes: 49 additions & 0 deletions cc/resources/video_resource_updater_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -504,5 +504,54 @@ TEST_F(VideoResourceUpdaterTest, CreateForHardwarePlanes_StreamTexture) {
EXPECT_FALSE(context3d_->WasImmutableTextureCreated());
}

namespace {

// Convert an IEEE 754 half-float to a double value
// that we can do math on.
double FromHalfFloat(uint16_t half_float) {
if (!half_float)
return 0.0;
int sign = (half_float & 0x8000) ? -1 : 1;
int exponent = (half_float >> 10) & 0x1F;
int fraction = half_float & 0x3FF;
if (exponent == 0) {
return pow(2.0, -24.0) * fraction;
} else if (exponent == 0x1F) {
return sign * 1000000000000.0;
} else {
return pow(2.0, exponent - 25) * (0x400 + fraction);
}
}

} // namespace

TEST_F(VideoResourceUpdaterTest, MakeHalfFloatTest) {
unsigned short integers[1 << 12];
unsigned short half_floats[1 << 12];
for (int bits = 9; bits <= 12; bits++) {
int num_values = 1 << bits;
for (int i = 0; i < num_values; i++)
integers[i] = i;

VideoResourceUpdater::MakeHalfFloats(integers, bits, num_values,
half_floats);

// Multiplier to converting integers to 0.0..1.0 range.
double multiplier = 1.0 / (num_values - 1);

for (int i = 0; i < num_values; i++) {
// We expect the result to be within +/- one least-significant bit.
// Within the range we care about, half-floats values and
// their representation both sort in the same order, so we
// can just add one to get the next bigger half-float.
float expected_precision =
FromHalfFloat(half_floats[i] + 1) - FromHalfFloat(half_floats[i]);
EXPECT_NEAR(FromHalfFloat(half_floats[i]), integers[i] * multiplier,
expected_precision)
<< "i = " << i << " bits = " << bits;
}
}
}

} // namespace
} // namespace cc
4 changes: 4 additions & 0 deletions content/browser/media/media_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearSilentWebm) {
IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearHighBitDepthVP9) {
PlayVideo("bear-320x180-hi10p-vp9.webm", GetParam());
}

IN_PROC_BROWSER_TEST_P(MediaTest, VideoBear12DepthVP9) {
PlayVideo("bear-320x180-hi12p-vp9.webm", GetParam());
}
#endif

#if defined(USE_PROPRIETARY_CODECS)
Expand Down
11 changes: 10 additions & 1 deletion media/base/video_frame.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ bool VideoFrame::IsValidConfig(VideoPixelFormat format,
return true;

// Make sure new formats are properly accounted for in the method.
static_assert(PIXEL_FORMAT_MAX == 21,
static_assert(PIXEL_FORMAT_MAX == 24,
"Added pixel format, please review IsValidConfig()");

if (format == PIXEL_FORMAT_UNKNOWN) {
Expand Down Expand Up @@ -524,6 +524,9 @@ size_t VideoFrame::NumPlanes(VideoPixelFormat format) {
case PIXEL_FORMAT_YUV420P10:
case PIXEL_FORMAT_YUV422P10:
case PIXEL_FORMAT_YUV444P10:
case PIXEL_FORMAT_YUV420P12:
case PIXEL_FORMAT_YUV422P12:
case PIXEL_FORMAT_YUV444P12:
return 3;
case PIXEL_FORMAT_YV12A:
return 4;
Expand Down Expand Up @@ -1014,11 +1017,13 @@ gfx::Size VideoFrame::SampleSize(VideoPixelFormat format, size_t plane) {
case PIXEL_FORMAT_YV24:
case PIXEL_FORMAT_YUV444P9:
case PIXEL_FORMAT_YUV444P10:
case PIXEL_FORMAT_YUV444P12:
return gfx::Size(1, 1);

case PIXEL_FORMAT_YV16:
case PIXEL_FORMAT_YUV422P9:
case PIXEL_FORMAT_YUV422P10:
case PIXEL_FORMAT_YUV422P12:
return gfx::Size(2, 1);

case PIXEL_FORMAT_YV12:
Expand All @@ -1029,6 +1034,7 @@ gfx::Size VideoFrame::SampleSize(VideoPixelFormat format, size_t plane) {
case PIXEL_FORMAT_MT21:
case PIXEL_FORMAT_YUV420P9:
case PIXEL_FORMAT_YUV420P10:
case PIXEL_FORMAT_YUV420P12:
return gfx::Size(2, 2);

case PIXEL_FORMAT_UNKNOWN:
Expand Down Expand Up @@ -1064,6 +1070,9 @@ int VideoFrame::BytesPerElement(VideoPixelFormat format, size_t plane) {
case PIXEL_FORMAT_YUV420P10:
case PIXEL_FORMAT_YUV422P10:
case PIXEL_FORMAT_YUV444P10:
case PIXEL_FORMAT_YUV420P12:
case PIXEL_FORMAT_YUV422P12:
case PIXEL_FORMAT_YUV444P12:
return 2;
case PIXEL_FORMAT_NV12:
case PIXEL_FORMAT_NV21:
Expand Down
12 changes: 12 additions & 0 deletions media/base/video_types.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ std::string VideoPixelFormatToString(VideoPixelFormat format) {
return "PIXEL_FORMAT_YUV444P9";
case PIXEL_FORMAT_YUV444P10:
return "PIXEL_FORMAT_YUV444P10";
case PIXEL_FORMAT_YUV420P12:
return "PIXEL_FORMAT_YUV420P12";
case PIXEL_FORMAT_YUV422P12:
return "PIXEL_FORMAT_YUV422P12";
case PIXEL_FORMAT_YUV444P12:
return "PIXEL_FORMAT_YUV444P12";
}
NOTREACHED() << "Invalid VideoPixelFormat provided: " << format;
return "";
Expand All @@ -75,6 +81,9 @@ bool IsYuvPlanar(VideoPixelFormat format) {
case PIXEL_FORMAT_YUV422P10:
case PIXEL_FORMAT_YUV444P9:
case PIXEL_FORMAT_YUV444P10:
case PIXEL_FORMAT_YUV420P12:
case PIXEL_FORMAT_YUV422P12:
case PIXEL_FORMAT_YUV444P12:
return true;

case PIXEL_FORMAT_UNKNOWN:
Expand Down Expand Up @@ -111,6 +120,9 @@ bool IsOpaque(VideoPixelFormat format) {
case PIXEL_FORMAT_YUV422P10:
case PIXEL_FORMAT_YUV444P9:
case PIXEL_FORMAT_YUV444P10:
case PIXEL_FORMAT_YUV420P12:
case PIXEL_FORMAT_YUV422P12:
case PIXEL_FORMAT_YUV444P12:
return true;
case PIXEL_FORMAT_YV12A:
case PIXEL_FORMAT_ARGB:
Expand Down
6 changes: 5 additions & 1 deletion media/base/video_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ enum VideoPixelFormat {
PIXEL_FORMAT_YUV444P9 = 20,
PIXEL_FORMAT_YUV444P10 = 21,

PIXEL_FORMAT_YUV420P12 = 22,
PIXEL_FORMAT_YUV422P12 = 23,
PIXEL_FORMAT_YUV444P12 = 24,

// Please update UMA histogram enumeration when adding new formats here.
PIXEL_FORMAT_MAX =
PIXEL_FORMAT_YUV444P10, // Must always be equal to largest entry logged.
PIXEL_FORMAT_YUV444P12, // Must always be equal to largest entry logged.
};

// Color space or color range used for the pixels.
Expand Down
12 changes: 12 additions & 0 deletions media/ffmpeg/ffmpeg_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -653,16 +653,22 @@ VideoPixelFormat AVPixelFormatToVideoPixelFormat(AVPixelFormat pixel_format) {
return PIXEL_FORMAT_YUV420P9;
case AV_PIX_FMT_YUV420P10LE:
return PIXEL_FORMAT_YUV420P10;
case AV_PIX_FMT_YUV420P12LE:
return PIXEL_FORMAT_YUV420P12;

case AV_PIX_FMT_YUV422P9LE:
return PIXEL_FORMAT_YUV422P9;
case AV_PIX_FMT_YUV422P10LE:
return PIXEL_FORMAT_YUV422P10;
case AV_PIX_FMT_YUV422P12LE:
return PIXEL_FORMAT_YUV422P12;

case AV_PIX_FMT_YUV444P9LE:
return PIXEL_FORMAT_YUV444P9;
case AV_PIX_FMT_YUV444P10LE:
return PIXEL_FORMAT_YUV444P10;
case AV_PIX_FMT_YUV444P12LE:
return PIXEL_FORMAT_YUV444P12;

default:
DVLOG(1) << "Unsupported AVPixelFormat: " << pixel_format;
Expand All @@ -684,14 +690,20 @@ AVPixelFormat VideoPixelFormatToAVPixelFormat(VideoPixelFormat video_format) {
return AV_PIX_FMT_YUV420P9LE;
case PIXEL_FORMAT_YUV420P10:
return AV_PIX_FMT_YUV420P10LE;
case PIXEL_FORMAT_YUV420P12:
return AV_PIX_FMT_YUV420P12LE;
case PIXEL_FORMAT_YUV422P9:
return AV_PIX_FMT_YUV422P9LE;
case PIXEL_FORMAT_YUV422P10:
return AV_PIX_FMT_YUV422P10LE;
case PIXEL_FORMAT_YUV422P12:
return AV_PIX_FMT_YUV422P12LE;
case PIXEL_FORMAT_YUV444P9:
return AV_PIX_FMT_YUV444P9LE;
case PIXEL_FORMAT_YUV444P10:
return AV_PIX_FMT_YUV444P10LE;
case PIXEL_FORMAT_YUV444P12:
return AV_PIX_FMT_YUV444P12LE;

default:
DVLOG(1) << "Unsupported Format: " << video_format;
Expand Down
3 changes: 2 additions & 1 deletion media/filters/ffmpeg_video_decoder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ int FFmpegVideoDecoder::GetVideoBuffer(struct AVCodecContext* codec_context,
format == PIXEL_FORMAT_YV24 || format == PIXEL_FORMAT_YUV420P9 ||
format == PIXEL_FORMAT_YUV420P10 || format == PIXEL_FORMAT_YUV422P9 ||
format == PIXEL_FORMAT_YUV422P10 || format == PIXEL_FORMAT_YUV444P9 ||
format == PIXEL_FORMAT_YUV444P10);
format == PIXEL_FORMAT_YUV444P10 || format == PIXEL_FORMAT_YUV420P12 ||
format == PIXEL_FORMAT_YUV422P12 || format == PIXEL_FORMAT_YUV444P12);

gfx::Size size(codec_context->width, codec_context->height);
const int ret = av_image_check_size(size.width(), size.height(), 0, NULL);
Expand Down
Loading

0 comments on commit 6cf2c09

Please sign in to comment.