#include "media/gpu/vaapi/vaapi_jpeg_encoder.h"
#include <stddef.h>
#include <string.h>
#include <algorithm>
#include <array>
#include <type_traits>
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/numerics/safe_conversions.h"
#include "media/gpu/macros.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#include "media/parsers/jpeg_parser.h"
namespace media {
namespace {
void FillPictureParameters(const gfx::Size& input_size,
int quality,
VABufferID output_buffer_id,
VAEncPictureParameterBufferJPEG* pic_param) {
pic_param->picture_width = input_size.width();
pic_param->picture_height = input_size.height();
pic_param->num_components = 3;
pic_param->coded_buf = output_buffer_id;
pic_param->quality = quality;
pic_param->pic_flags.bits.profile = 0;
pic_param->pic_flags.bits.progressive = 0;
pic_param->pic_flags.bits.huffman = 1;
pic_param->pic_flags.bits.interleaved = 0;
pic_param->pic_flags.bits.differential = 0;
pic_param->sample_bit_depth = 8;
pic_param->num_scan = 1;
}
void FillQMatrix(VAQMatrixBufferJPEG* q_matrix) {
const JpegQuantizationTable& luminance = kDefaultQuantTable[0];
static_assert(std::extent<decltype(luminance.value)>() ==
std::extent<decltype(q_matrix->lum_quantiser_matrix)>(),
"Luminance quantization table size mismatch.");
static_assert(std::size(kZigZag8x8) == std::size(luminance.value),
"Luminance quantization table size mismatch.");
q_matrix->load_lum_quantiser_matrix = 1;
for (size_t i = 0; i < std::size(kZigZag8x8); i++) {
UNSAFE_TODO(q_matrix->lum_quantiser_matrix[i]) =
UNSAFE_TODO(luminance.value[kZigZag8x8[i]]);
}
const JpegQuantizationTable& chrominance = kDefaultQuantTable[1];
static_assert(std::extent<decltype(chrominance.value)>() ==
std::extent<decltype(q_matrix->chroma_quantiser_matrix)>(),
"Chrominance quantization table size mismatch.");
static_assert(std::size(kZigZag8x8) == std::size(chrominance.value),
"Chrominance quantization table size mismatch.");
q_matrix->load_chroma_quantiser_matrix = 1;
for (size_t i = 0; i < std::size(kZigZag8x8); i++) {
UNSAFE_TODO(q_matrix->chroma_quantiser_matrix[i]) =
UNSAFE_TODO(chrominance.value[kZigZag8x8[i]]);
}
}
void FillHuffmanTableParameters(
VAHuffmanTableBufferJPEGBaseline* huff_table_param) {
static_assert(std::size(kDefaultDcTable) == std::size(kDefaultAcTable),
"DC table and AC table size mismatch.");
static_assert(std::size(kDefaultDcTable) ==
std::extent<decltype(huff_table_param->huffman_table)>(),
"DC table and destination table size mismatch.");
for (size_t i = 0; i < std::size(kDefaultDcTable); ++i) {
const JpegHuffmanTable& dcTable = UNSAFE_TODO(kDefaultDcTable[i]);
const JpegHuffmanTable& acTable = UNSAFE_TODO(kDefaultAcTable[i]);
UNSAFE_TODO(huff_table_param->load_huffman_table[i]) = true;
SafeArrayMemcpy(
UNSAFE_TODO(huff_table_param->huffman_table[i]).num_dc_codes,
dcTable.code_length);
static_assert(
std::extent<decltype(huff_table_param->huffman_table[i].dc_values)>() <=
std::extent<decltype(dcTable.code_value)>(),
"DC table code value array too small.");
UNSAFE_TODO(memcpy(huff_table_param->huffman_table[i].dc_values,
&dcTable.code_value[0],
sizeof(huff_table_param->huffman_table[i].dc_values)));
SafeArrayMemcpy(
UNSAFE_TODO(huff_table_param->huffman_table[i]).num_ac_codes,
acTable.code_length);
SafeArrayMemcpy(UNSAFE_TODO(huff_table_param->huffman_table[i]).ac_values,
acTable.code_value);
UNSAFE_TODO(memset(huff_table_param->huffman_table[i].pad, 0,
sizeof(huff_table_param->huffman_table[i].pad)));
}
}
void FillSliceParameters(VAEncSliceParameterBufferJPEG* slice_param) {
slice_param->restart_interval = 0;
slice_param->num_components = 3;
slice_param->components[0].component_selector = 1;
slice_param->components[0].dc_table_selector = 0;
slice_param->components[0].ac_table_selector = 0;
slice_param->components[1].component_selector = 2;
slice_param->components[1].dc_table_selector = 1;
slice_param->components[1].ac_table_selector = 1;
slice_param->components[2].component_selector = 3;
slice_param->components[2].dc_table_selector = 1;
slice_param->components[2].ac_table_selector = 1;
}
size_t FillJpegHeader(const gfx::Size& input_size,
const uint8_t* exif_buffer,
size_t exif_buffer_size,
int quality,
base::span<uint8_t> header,
size_t* exif_offset) {
unsigned int width = input_size.width();
unsigned int height = input_size.height();
size_t idx = 0;
static const uint8_t kSOI[] = {0xFF, JPEG_SOI};
UNSAFE_TODO(memcpy(header.data(), kSOI, sizeof(kSOI)));
idx += sizeof(kSOI);
if (exif_buffer_size > 0) {
uint16_t exif_segment_size = static_cast<uint16_t>(exif_buffer_size + 2);
const uint8_t kAppSegment[] = {
0xFF, JPEG_APP1, static_cast<uint8_t>(exif_segment_size / 256),
static_cast<uint8_t>(exif_segment_size % 256)};
UNSAFE_TODO(
memcpy(header.subspan(idx).data(), kAppSegment, sizeof(kAppSegment)));
idx += sizeof(kAppSegment);
*exif_offset = idx;
UNSAFE_TODO(
memcpy(header.subspan(idx).data(), exif_buffer, exif_buffer_size));
idx += exif_buffer_size;
} else {
static const uint8_t kAppSegment[] = {
0xFF, JPEG_APP0, 0x00,
0x10,
0x4A,
0x46,
0x49,
0x46,
0x00,
0x01,
0x01,
0x01,
0x00,
0x48,
0x00,
0x48,
0x00,
0x00
};
UNSAFE_TODO(
memcpy(header.subspan(idx).data(), kAppSegment, sizeof(kAppSegment)));
idx += sizeof(kAppSegment);
}
if (quality <= 0) {
quality = 1;
}
uint32_t quality_normalized = base::saturated_cast<uint32_t>(
(quality < 50) ? (5000 / quality) : (200 - (quality * 2)));
for (size_t i = 0; i < 2; ++i) {
const uint8_t kQuantSegment[] = {
0xFF, JPEG_DQT, 0x00,
0x03 + kDctSize,
static_cast<uint8_t>(i)
};
UNSAFE_TODO(memcpy(header.subspan(idx).data(), kQuantSegment,
sizeof(kQuantSegment)));
idx += sizeof(kQuantSegment);
const JpegQuantizationTable& quant_table =
UNSAFE_TODO(kDefaultQuantTable[i]);
for (size_t j = 0; j < kDctSize; ++j) {
const static uint32_t shift =
VaapiWrapper::GetImplementationType() == VAImplementation::kIntelIHD ? 50 : 0;
uint32_t scaled_quant_value =
(UNSAFE_TODO(quant_table.value[kZigZag8x8[j]]) * quality_normalized +
shift) /
100;
scaled_quant_value = std::clamp(scaled_quant_value, 1u, 255u);
header[idx++] = static_cast<uint8_t>(scaled_quant_value);
}
}
const uint8_t kStartOfFrame[] = {
0xFF,
JPEG_SOF0,
0x00,
0x11,
8,
static_cast<uint8_t>((height >> 8) & 0xFF),
static_cast<uint8_t>(height & 0xFF),
static_cast<uint8_t>((width >> 8) & 0xFF),
static_cast<uint8_t>(width & 0xFF),
0x03,
};
UNSAFE_TODO(
memcpy(header.subspan(idx).data(), kStartOfFrame, sizeof(kStartOfFrame)));
idx += sizeof(kStartOfFrame);
for (uint8_t i = 0; i < 3; ++i) {
uint8_t h_sample_factor = 1;
uint8_t v_sample_factor = 1;
uint8_t quant_table_number = 1;
if (!i) {
h_sample_factor = 2;
v_sample_factor = 2;
quant_table_number = 0;
}
header[idx++] = i + 1;
header[idx++] = (h_sample_factor << 4) | v_sample_factor;
header[idx++] = quant_table_number;
}
static const uint8_t kDcSegment[] = {
0xFF, JPEG_DHT, 0x00,
0x1F,
};
static const uint8_t kAcSegment[] = {
0xFF, JPEG_DHT, 0x00,
0xB5,
};
for (size_t i = 0; i < 2; ++i) {
UNSAFE_TODO(
memcpy(header.subspan(idx).data(), kDcSegment, sizeof(kDcSegment)));
idx += sizeof(kDcSegment);
header[idx++] = static_cast<uint8_t>(i);
const JpegHuffmanTable& dcTable = UNSAFE_TODO(kDefaultDcTable[i]);
for (size_t j = 0; j < kNumDcRunSizeBits; ++j)
header[idx++] = UNSAFE_TODO(dcTable.code_length[j]);
for (size_t j = 0; j < kNumDcCodeWordsHuffVal; ++j)
header[idx++] = UNSAFE_TODO(dcTable.code_value[j]);
UNSAFE_TODO(
memcpy(header.subspan(idx).data(), kAcSegment, sizeof(kAcSegment)));
idx += sizeof(kAcSegment);
header[idx++] = 0x10 | static_cast<uint8_t>(i);
const JpegHuffmanTable& acTable = UNSAFE_TODO(kDefaultAcTable[i]);
for (size_t j = 0; j < kNumAcRunSizeBits; ++j)
header[idx++] = UNSAFE_TODO(acTable.code_length[j]);
for (size_t j = 0; j < kNumAcCodeWordsHuffVal; ++j)
header[idx++] = UNSAFE_TODO(acTable.code_value[j]);
}
static const uint8_t kStartOfScan[] = {
0xFF, JPEG_SOS, 0x00,
0x0C,
0x03
};
UNSAFE_TODO(
memcpy(header.subspan(idx).data(), kStartOfScan, sizeof(kStartOfScan)));
idx += sizeof(kStartOfScan);
for (uint8_t i = 0; i < 3; ++i) {
uint8_t dc_table_number = 1;
uint8_t ac_table_number = 1;
if (!i) {
dc_table_number = 0;
ac_table_number = 0;
}
header[idx++] = i + 1;
header[idx++] = (dc_table_number << 4) | ac_table_number;
}
header[idx++] = 0x00;
header[idx++] = 0x3F;
header[idx++] = 0x00;
return idx << 3;
}
}
VaapiJpegEncoder::VaapiJpegEncoder(scoped_refptr<VaapiWrapper> vaapi_wrapper)
: vaapi_wrapper_(vaapi_wrapper),
q_matrix_cached_(nullptr),
huff_table_param_cached_(nullptr),
slice_param_cached_(nullptr) {}
VaapiJpegEncoder::~VaapiJpegEncoder() {}
size_t VaapiJpegEncoder::GetMaxCodedBufferSize(const gfx::Size& size) {
return size.GetArea() * 3 / 2 + kJpegDefaultHeaderSize;
}
bool VaapiJpegEncoder::Encode(const gfx::Size& input_size,
const uint8_t* exif_buffer,
size_t exif_buffer_size,
int quality,
VASurfaceID surface_id,
VABufferID output_buffer_id,
size_t* exif_offset) {
DCHECK_NE(surface_id, VA_INVALID_SURFACE);
if (input_size.width() > kMaxDimension ||
input_size.height() > kMaxDimension) {
return false;
}
VAEncPictureParameterBufferJPEG pic_param;
FillPictureParameters(input_size, quality, output_buffer_id, &pic_param);
if (!q_matrix_cached_) {
q_matrix_cached_.reset(new VAQMatrixBufferJPEG());
FillQMatrix(q_matrix_cached_.get());
}
if (!huff_table_param_cached_) {
huff_table_param_cached_.reset(new VAHuffmanTableBufferJPEGBaseline());
FillHuffmanTableParameters(huff_table_param_cached_.get());
}
if (!slice_param_cached_) {
slice_param_cached_.reset(new VAEncSliceParameterBufferJPEG());
FillSliceParameters(slice_param_cached_.get());
}
size_t jpeg_header_size =
exif_buffer_size > 0
? kJpegDefaultHeaderSize + kJFIFApp1HeaderSize + exif_buffer_size
: kJpegDefaultHeaderSize + kJFIFApp0Size;
std::vector<uint8_t> jpeg_header(jpeg_header_size);
const size_t length_in_bits =
FillJpegHeader(input_size, exif_buffer, exif_buffer_size, quality,
jpeg_header, exif_offset);
VAEncPackedHeaderParameterBuffer header_param = {};
header_param.type = VAEncPackedHeaderRawData;
header_param.bit_length = length_in_bits;
header_param.has_emulation_bytes = 0;
if (!vaapi_wrapper_->SubmitBuffers(
{{VAEncPictureParameterBufferType, sizeof(pic_param), &pic_param},
{VAQMatrixBufferType, sizeof(*q_matrix_cached_),
q_matrix_cached_.get()},
{VAHuffmanTableBufferType, sizeof(*huff_table_param_cached_),
huff_table_param_cached_.get()},
{VAEncSliceParameterBufferType, sizeof(*slice_param_cached_),
slice_param_cached_.get()},
{VAEncPackedHeaderParameterBufferType, sizeof(header_param),
&header_param},
{VAEncPackedHeaderDataBufferType, (length_in_bits + 7) / 8,
jpeg_header.data()}})) {
return false;
}
return vaapi_wrapper_->ExecuteAndDestroyPendingBuffers(surface_id);
}
}