blob: 7cd587e644522bdb1efb368764a1f5ff4fa056ea [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/v4l2/v4l2_jpeg_encode_accelerator.h"
#include <errno.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/cxx17_backports.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/bind_to_current_loop.h"
#include "media/gpu/chromeos/fourcc.h"
#include "media/gpu/chromeos/platform_video_frame_utils.h"
#include "media/gpu/macros.h"
#include "media/gpu/v4l2/v4l2_device.h"
#include "third_party/libyuv/include/libyuv.h"
#define IOCTL_OR_ERROR_RETURN_VALUE(type, arg, value, type_name) \
do { \
if (device_->Ioctl(type, arg) != 0) { \
VPLOGF(1) << "ioctl() failed: " << type_name; \
NotifyError(kInvalidBitstreamBufferId, PLATFORM_FAILURE); \
return value; \
} \
} while (0)
#define IOCTL_OR_ERROR_RETURN(type, arg) \
IOCTL_OR_ERROR_RETURN_VALUE(type, arg, ((void)0), #type)
#define IOCTL_OR_ERROR_RETURN_FALSE(type, arg) \
IOCTL_OR_ERROR_RETURN_VALUE(type, arg, false, #type)
#define IOCTL_OR_LOG_ERROR(type, arg) \
do { \
if (device_->Ioctl(type, arg) != 0) { \
VPLOGF(1) << "ioctl() failed: " << #type; \
NotifyError(kInvalidBitstreamBufferId, PLATFORM_FAILURE); \
} \
} while (0)
namespace media {
V4L2JpegEncodeAccelerator::I420BufferRecord::I420BufferRecord()
: at_device(false) {
memset(address, 0, sizeof(address));
memset(length, 0, sizeof(length));
}
V4L2JpegEncodeAccelerator::I420BufferRecord::~I420BufferRecord() {}
V4L2JpegEncodeAccelerator::JpegBufferRecord::JpegBufferRecord()
: at_device(false) {
memset(address, 0, sizeof(address));
memset(length, 0, sizeof(length));
}
V4L2JpegEncodeAccelerator::JpegBufferRecord::~JpegBufferRecord() {}
V4L2JpegEncodeAccelerator::JobRecord::JobRecord(
scoped_refptr<VideoFrame> input_frame,
scoped_refptr<VideoFrame> output_frame,
int quality,
int32_t task_id,
BitstreamBuffer* exif_buffer)
: input_frame(input_frame),
output_frame(output_frame),
quality(quality),
task_id(task_id),
output_shm(base::subtle::PlatformSharedMemoryRegion(), 0, true), // dummy
exif_shm(nullptr) {
if (exif_buffer) {
exif_shm.reset(new UnalignedSharedMemory(exif_buffer->TakeRegion(),
exif_buffer->size(), false));
exif_offset = exif_buffer->offset();
}
}
V4L2JpegEncodeAccelerator::JobRecord::JobRecord(
scoped_refptr<VideoFrame> input_frame,
int quality,
BitstreamBuffer* exif_buffer,
BitstreamBuffer output_buffer)
: input_frame(input_frame),
quality(quality),
task_id(output_buffer.id()),
output_shm(output_buffer.TakeRegion(), output_buffer.size(), false),
output_offset(output_buffer.offset()),
exif_shm(nullptr) {
if (exif_buffer) {
exif_shm.reset(new UnalignedSharedMemory(exif_buffer->TakeRegion(),
exif_buffer->size(), false));
exif_offset = exif_buffer->offset();
}
}
V4L2JpegEncodeAccelerator::JobRecord::~JobRecord() {}
V4L2JpegEncodeAccelerator::EncodedInstance::EncodedInstance(
V4L2JpegEncodeAccelerator* parent)
: parent_(parent),
input_streamon_(false),
output_streamon_(false),
input_buffer_pixelformat_(0),
input_buffer_num_planes_(0),
output_buffer_pixelformat_(0) {}
V4L2JpegEncodeAccelerator::EncodedInstance::~EncodedInstance() {}
void V4L2JpegEncodeAccelerator::EncodedInstance::DestroyTask() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
while (!input_job_queue_.empty())
input_job_queue_.pop();
while (!running_job_queue_.empty())
running_job_queue_.pop();
DestroyInputBuffers();
DestroyOutputBuffers();
}
bool V4L2JpegEncodeAccelerator::EncodedInstance::Initialize() {
device_ = V4L2Device::Create();
if (!device_) {
VLOGF(1) << "Failed to Create V4L2Device";
return false;
}
output_buffer_pixelformat_ = V4L2_PIX_FMT_JPEG_RAW;
if (!device_->Open(V4L2Device::Type::kJpegEncoder,
output_buffer_pixelformat_)) {
VLOGF(1) << "Failed to open device";
return false;
}
// Capabilities check.
struct v4l2_capability caps;
const __u32 kCapsRequired = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
memset(&caps, 0, sizeof(caps));
if (device_->Ioctl(VIDIOC_QUERYCAP, &caps) != 0) {
VPLOGF(1) << "ioctl() failed: VIDIOC_QUERYCAP";
return false;
}
if ((caps.capabilities & kCapsRequired) != kCapsRequired) {
VLOGF(1) << "VIDIOC_QUERYCAP, caps check failed: 0x" << std::hex
<< caps.capabilities;
return false;
}
return true;
}
// static
void V4L2JpegEncodeAccelerator::EncodedInstance::FillQuantizationTable(
int quality,
const uint8_t* basic_table,
uint8_t* dst_table) {
unsigned int temp;
if (quality < 50)
quality = 5000 / quality;
else
quality = 200 - quality * 2;
for (size_t i = 0; i < kDctSize; i++) {
temp = ((unsigned int)basic_table[kZigZag8x8[i]] * quality + 50) / 100;
/* limit the values to the valid range */
dst_table[i] = base::clamp(temp, 1u, 255u);
}
}
void V4L2JpegEncodeAccelerator::EncodedInstance::PrepareJpegMarkers(
gfx::Size coded_size) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
// Quantization Tables.
// i = 0 for Luminance
// i = 1 for Chrominance
const int kNumDQT = 2;
for (size_t i = 0; i < kNumDQT; ++i) {
const uint8_t kQuantSegment[] = {
0xFF, JPEG_DQT, 0x00,
0x03 + kDctSize, // Segment length:67 (2-byte).
static_cast<uint8_t>(i) // Precision (4-bit high) = 0,
// Index (4-bit low) = i.
};
for (size_t j = 0; j < sizeof(kQuantSegment); ++j) {
jpeg_markers_.push_back(kQuantSegment[j]);
}
for (size_t j = 0; j < kDctSize; ++j) {
jpeg_markers_.push_back(quantization_table_[i].value[j]);
}
}
// Start of Frame - Baseline.
const int kNumOfComponents = 3;
const uint8_t kStartOfFrame[] = {
0xFF,
JPEG_SOF0, // Baseline.
0x00,
0x11, // Segment length:17 (2-byte).
8, // Data precision.
static_cast<uint8_t>((coded_size.height() >> 8) & 0xFF),
static_cast<uint8_t>(coded_size.height() & 0xFF),
static_cast<uint8_t>((coded_size.width() >> 8) & 0xFF),
static_cast<uint8_t>(coded_size.width() & 0xFF),
kNumOfComponents,
};
for (size_t i = 0; i < sizeof(kStartOfFrame); ++i) {
jpeg_markers_.push_back(kStartOfFrame[i]);
}
// i = 0 for Y Plane
// i = 1 for U Plane
// i = 2 for V Plane
for (size_t i = 0; i < kNumOfComponents; ++i) {
// These are the values for U and V planes.
uint8_t h_sample_factor = 1;
uint8_t v_sample_factor = 1;
uint8_t quant_table_number = 1;
if (!i) {
// These are the values for Y plane.
h_sample_factor = 2;
v_sample_factor = 2;
quant_table_number = 0;
}
jpeg_markers_.push_back(i + 1);
// Horizontal Sample Factor (4-bit high),
// Vertical Sample Factor (4-bit low).
jpeg_markers_.push_back((h_sample_factor << 4) | v_sample_factor);
jpeg_markers_.push_back(quant_table_number);
}
// Huffman Tables.
static const uint8_t kDcSegment[] = {
0xFF, JPEG_DHT, 0x00,
0x1F, // Segment length:31 (2-byte).
};
static const uint8_t kAcSegment[] = {
0xFF, JPEG_DHT, 0x00,
0xB5, // Segment length:181 (2-byte).
};
// i = 0 for Luminance
// i = 1 for Chrominance
const int kNumHuffmanTables = 2;
for (size_t i = 0; i < kNumHuffmanTables; ++i) {
// DC Table.
for (size_t j = 0; j < sizeof(kDcSegment); ++j) {
jpeg_markers_.push_back(kDcSegment[j]);
}
// Type (4-bit high) = 0:DC, Index (4-bit low).
jpeg_markers_.push_back(static_cast<uint8_t>(i));
const JpegHuffmanTable& dcTable = kDefaultDcTable[i];
for (size_t j = 0; j < kNumDcRunSizeBits; ++j)
jpeg_markers_.push_back(dcTable.code_length[j]);
for (size_t j = 0; j < kNumDcCodeWordsHuffVal; ++j)
jpeg_markers_.push_back(dcTable.code_value[j]);
// AC Table.
for (size_t j = 0; j < sizeof(kAcSegment); ++j) {
jpeg_markers_.push_back(kAcSegment[j]);
}
// Type (4-bit high) = 1:AC, Index (4-bit low).
jpeg_markers_.push_back(0x10 | static_cast<uint8_t>(i));
const JpegHuffmanTable& acTable = kDefaultAcTable[i];
for (size_t j = 0; j < kNumAcRunSizeBits; ++j)
jpeg_markers_.push_back(acTable.code_length[j]);
for (size_t j = 0; j < kNumAcCodeWordsHuffVal; ++j)
jpeg_markers_.push_back(acTable.code_value[j]);
}
// Start of Scan.
static const uint8_t kStartOfScan[] = {
0xFF, JPEG_SOS, 0x00,
0x0C, // Segment Length:12 (2-byte).
0x03 // Number of components in scan.
};
for (size_t i = 0; i < sizeof(kStartOfScan); ++i) {
jpeg_markers_.push_back(kStartOfScan[i]);
}
// i = 0 for Y Plane
// i = 1 for U Plane
// i = 2 for V Plane
for (uint8_t i = 0; i < kNumOfComponents; ++i) {
uint8_t dc_table_number = 1;
uint8_t ac_table_number = 1;
if (!i) {
dc_table_number = 0;
ac_table_number = 0;
}
jpeg_markers_.push_back(i + 1);
// DC Table Selector (4-bit high), AC Table Selector (4-bit low).
jpeg_markers_.push_back((dc_table_number << 4) | ac_table_number);
}
jpeg_markers_.push_back(0x00); // 0 for Baseline.
jpeg_markers_.push_back(0x3F); // 63 for Baseline.
jpeg_markers_.push_back(0x00); // 0 for Baseline.
}
bool V4L2JpegEncodeAccelerator::EncodedInstance::SetUpJpegParameters(
int quality,
gfx::Size coded_size) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
struct v4l2_ext_controls ctrls;
struct v4l2_ext_control ctrl;
memset(&ctrls, 0, sizeof(ctrls));
memset(&ctrl, 0, sizeof(ctrl));
ctrls.ctrl_class = V4L2_CTRL_CLASS_JPEG;
ctrls.controls = &ctrl;
ctrls.count = 1;
switch (output_buffer_pixelformat_) {
case V4L2_PIX_FMT_JPEG_RAW:
FillQuantizationTable(quality, kDefaultQuantTable[0].value,
quantization_table_[0].value);
FillQuantizationTable(quality, kDefaultQuantTable[1].value,
quantization_table_[1].value);
ctrl.id = V4L2_CID_JPEG_LUMA_QUANTIZATION;
ctrl.size = kDctSize;
ctrl.ptr = quantization_table_[0].value;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_EXT_CTRLS, &ctrls);
ctrl.id = V4L2_CID_JPEG_CHROMA_QUANTIZATION;
ctrl.size = kDctSize;
ctrl.ptr = quantization_table_[1].value;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_EXT_CTRLS, &ctrls);
// We need to prepare our own JPEG Markers.
PrepareJpegMarkers(coded_size);
break;
default:
NOTREACHED();
}
return true;
}
size_t V4L2JpegEncodeAccelerator::EncodedInstance::InputBufferQueuedCount() {
return input_buffer_map_.size() - free_input_buffers_.size();
}
size_t V4L2JpegEncodeAccelerator::EncodedInstance::OutputBufferQueuedCount() {
return output_buffer_map_.size() - free_output_buffers_.size();
}
bool V4L2JpegEncodeAccelerator::EncodedInstance::CreateBuffers(
gfx::Size coded_size,
size_t output_buffer_size) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
// The order of set output/input formats matters.
// rk3399 reset input format when we set output format.
if (!SetOutputBufferFormat(coded_size, output_buffer_size)) {
return false;
}
if (!SetInputBufferFormat(coded_size)) {
return false;
}
if (!RequestInputBuffers()) {
return false;
}
if (!RequestOutputBuffers()) {
return false;
}
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstance::SetInputBufferFormat(
gfx::Size coded_size) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
DCHECK(!input_streamon_);
DCHECK(input_job_queue_.empty());
constexpr uint32_t input_pix_fmt_candidates[] = {
V4L2_PIX_FMT_YUV420M,
V4L2_PIX_FMT_YUV420,
};
struct v4l2_format format;
input_buffer_pixelformat_ = 0;
for (const auto input_pix_fmt : input_pix_fmt_candidates) {
DCHECK_EQ(Fourcc::FromV4L2PixFmt(input_pix_fmt)->ToVideoPixelFormat(),
PIXEL_FORMAT_I420);
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
format.fmt.pix_mp.num_planes = kMaxI420Plane;
format.fmt.pix_mp.pixelformat = input_pix_fmt;
format.fmt.pix_mp.field = V4L2_FIELD_ANY;
format.fmt.pix_mp.width = coded_size.width();
format.fmt.pix_mp.height = coded_size.height();
if (device_->Ioctl(VIDIOC_S_FMT, &format) == 0 &&
format.fmt.pix_mp.pixelformat == input_pix_fmt) {
// Save V4L2 returned values.
input_buffer_pixelformat_ = format.fmt.pix_mp.pixelformat;
input_buffer_num_planes_ = format.fmt.pix_mp.num_planes;
input_buffer_height_ = format.fmt.pix_mp.height;
break;
}
}
if (input_buffer_pixelformat_ == 0) {
VLOGF(1) << "Neither YUV420 nor YUV420M is supported.";
return false;
}
if (format.fmt.pix_mp.width != static_cast<uint32_t>(coded_size.width()) ||
format.fmt.pix_mp.height != static_cast<uint32_t>(coded_size.height())) {
VLOGF(1) << "Width " << coded_size.width() << "->"
<< format.fmt.pix_mp.width << ",Height " << coded_size.height()
<< "->" << format.fmt.pix_mp.height;
return false;
}
for (int i = 0; i < format.fmt.pix_mp.num_planes; i++) {
bytes_per_line_[i] = format.fmt.pix_mp.plane_fmt[i].bytesperline;
VLOGF(3) << "Bytes Per Line:" << bytes_per_line_[i];
}
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstance::SetOutputBufferFormat(
gfx::Size coded_size,
size_t buffer_size) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
DCHECK(!output_streamon_);
DCHECK(running_job_queue_.empty());
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
format.fmt.pix_mp.num_planes = kMaxJpegPlane;
format.fmt.pix_mp.pixelformat = output_buffer_pixelformat_;
format.fmt.pix_mp.field = V4L2_FIELD_ANY;
format.fmt.pix_mp.plane_fmt[0].sizeimage = buffer_size;
format.fmt.pix_mp.width = coded_size.width();
format.fmt.pix_mp.height = coded_size.height();
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format);
DCHECK_EQ(format.fmt.pix_mp.pixelformat, output_buffer_pixelformat_);
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstance::RequestInputBuffers() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
format.fmt.pix_mp.pixelformat = input_buffer_pixelformat_;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_G_FMT, &format);
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));
reqbufs.count = kBufferCount;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
reqbufs.memory = V4L2_MEMORY_MMAP;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs);
DCHECK(input_buffer_map_.empty());
input_buffer_map_.resize(reqbufs.count);
for (size_t i = 0; i < input_buffer_map_.size(); ++i) {
free_input_buffers_.push_back(i);
struct v4l2_buffer buffer;
struct v4l2_plane planes[kMaxI420Plane];
memset(&buffer, 0, sizeof(buffer));
memset(planes, 0, sizeof(planes));
buffer.index = i;
buffer.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.m.planes = planes;
buffer.length = base::size(planes);
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QUERYBUF, &buffer);
if (input_buffer_num_planes_ != buffer.length) {
return false;
}
for (size_t j = 0; j < buffer.length; ++j) {
if (base::checked_cast<int64_t>(planes[j].length) <
VideoFrame::PlaneSize(
PIXEL_FORMAT_I420, j,
gfx::Size(format.fmt.pix_mp.width, format.fmt.pix_mp.height))
.GetArea()) {
return false;
}
void* address =
device_->Mmap(NULL, planes[j].length, PROT_READ | PROT_WRITE,
MAP_SHARED, planes[j].m.mem_offset);
if (address == MAP_FAILED) {
VPLOGF(1) << "mmap() failed";
return false;
}
input_buffer_map_[i].address[j] = address;
input_buffer_map_[i].length[j] = planes[j].length;
}
}
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstance::RequestOutputBuffers() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));
reqbufs.count = kBufferCount;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
reqbufs.memory = V4L2_MEMORY_MMAP;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs);
DCHECK(output_buffer_map_.empty());
output_buffer_map_.resize(reqbufs.count);
for (size_t i = 0; i < output_buffer_map_.size(); ++i) {
free_output_buffers_.push_back(i);
struct v4l2_buffer buffer;
struct v4l2_plane planes[kMaxJpegPlane];
memset(&buffer, 0, sizeof(buffer));
memset(planes, 0, sizeof(planes));
buffer.index = i;
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buffer.m.planes = planes;
buffer.length = base::size(planes);
buffer.memory = V4L2_MEMORY_MMAP;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QUERYBUF, &buffer);
if (buffer.length != kMaxJpegPlane) {
return false;
}
void* address =
device_->Mmap(NULL, planes[0].length, PROT_READ | PROT_WRITE,
MAP_SHARED, planes[0].m.mem_offset);
if (address == MAP_FAILED) {
VPLOGF(1) << "mmap() failed";
return false;
}
output_buffer_map_[i].address[0] = address;
output_buffer_map_[i].length[0] = planes[0].length;
}
return true;
}
void V4L2JpegEncodeAccelerator::EncodedInstance::DestroyInputBuffers() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
free_input_buffers_.clear();
if (input_buffer_map_.empty())
return;
if (input_streamon_) {
__u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMOFF, &type);
input_streamon_ = false;
}
for (const auto& input_record : input_buffer_map_) {
for (size_t i = 0; i < input_buffer_num_planes_; ++i) {
device_->Munmap(input_record.address[i], input_record.length[i]);
}
}
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));
reqbufs.count = 0;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
reqbufs.memory = V4L2_MEMORY_MMAP;
IOCTL_OR_LOG_ERROR(VIDIOC_REQBUFS, &reqbufs);
input_buffer_map_.clear();
input_buffer_num_planes_ = 0;
}
void V4L2JpegEncodeAccelerator::EncodedInstance::DestroyOutputBuffers() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
free_output_buffers_.clear();
if (output_buffer_map_.empty())
return;
if (output_streamon_) {
__u32 type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMOFF, &type);
output_streamon_ = false;
}
for (const auto& output_record : output_buffer_map_) {
device_->Munmap(output_record.address[0], output_record.length[0]);
}
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));
reqbufs.count = 0;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
reqbufs.memory = V4L2_MEMORY_MMAP;
IOCTL_OR_LOG_ERROR(VIDIOC_REQBUFS, &reqbufs);
output_buffer_map_.clear();
}
void V4L2JpegEncodeAccelerator::EncodedInstance::ServiceDevice() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
if (!running_job_queue_.empty()) {
Dequeue();
}
EnqueueInput();
EnqueueOutput();
DVLOGF(3) << "buffer counts: INPUT[" << input_job_queue_.size()
<< "] => DEVICE[" << free_input_buffers_.size() << "/"
<< input_buffer_map_.size() << "->" << free_output_buffers_.size()
<< "/" << output_buffer_map_.size() << "]";
}
void V4L2JpegEncodeAccelerator::EncodedInstance::EnqueueInput() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
while (!input_job_queue_.empty() && !free_input_buffers_.empty()) {
if (!EnqueueInputRecord())
return;
}
if (!input_streamon_ && InputBufferQueuedCount()) {
__u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMON, &type);
input_streamon_ = true;
}
}
void V4L2JpegEncodeAccelerator::EncodedInstance::EnqueueOutput() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
while (running_job_queue_.size() > OutputBufferQueuedCount() &&
!free_output_buffers_.empty()) {
if (!EnqueueOutputRecord())
return;
}
if (!output_streamon_ && OutputBufferQueuedCount()) {
__u32 type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMON, &type);
output_streamon_ = true;
}
}
bool V4L2JpegEncodeAccelerator::EncodedInstance::EnqueueInputRecord() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
DCHECK(!input_job_queue_.empty());
DCHECK(!free_input_buffers_.empty());
// Enqueue an input (VIDEO_OUTPUT) buffer for an input video frame.
std::unique_ptr<JobRecord> job_record = std::move(input_job_queue_.front());
input_job_queue_.pop();
const int index = free_input_buffers_.back();
I420BufferRecord& input_record = input_buffer_map_[index];
DCHECK(!input_record.at_device);
// Copy image from user memory to MMAP memory.
uint8_t* src_y = job_record->input_frame->data(VideoFrame::kYPlane);
uint8_t* src_u = job_record->input_frame->data(VideoFrame::kUPlane);
uint8_t* src_v = job_record->input_frame->data(VideoFrame::kVPlane);
size_t src_y_stride = job_record->input_frame->stride(VideoFrame::kYPlane);
size_t src_u_stride = job_record->input_frame->stride(VideoFrame::kUPlane);
size_t src_v_stride = job_record->input_frame->stride(VideoFrame::kVPlane);
size_t input_coded_width = job_record->input_frame->coded_size().width();
size_t input_coded_height = job_record->input_frame->coded_size().height();
size_t dst_y_stride = bytes_per_line_[0];
size_t dst_u_stride;
size_t dst_v_stride;
uint8_t* dst_y = static_cast<uint8_t*>(input_record.address[0]);
uint8_t* dst_u;
uint8_t* dst_v;
if (input_buffer_num_planes_ == 1) {
dst_u_stride = dst_y_stride / 2;
dst_v_stride = dst_y_stride / 2;
dst_u = dst_y + dst_y_stride * input_buffer_height_;
dst_v = dst_u + dst_u_stride * input_buffer_height_ / 2;
} else {
DCHECK(input_buffer_num_planes_ == 3);
dst_u_stride = bytes_per_line_[1];
dst_v_stride = bytes_per_line_[2];
dst_u = static_cast<uint8_t*>(input_record.address[1]);
dst_v = static_cast<uint8_t*>(input_record.address[2]);
}
if (libyuv::I420Copy(src_y, src_y_stride, src_u, src_u_stride, src_v,
src_v_stride, dst_y, dst_y_stride, dst_u, dst_u_stride,
dst_v, dst_v_stride, input_coded_width,
input_coded_height)) {
VLOGF(1) << "I420Copy failed";
return false;
}
struct v4l2_buffer qbuf;
struct v4l2_plane planes[kMaxI420Plane];
memset(&qbuf, 0, sizeof(qbuf));
memset(planes, 0, sizeof(planes));
qbuf.index = index;
qbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
qbuf.memory = V4L2_MEMORY_MMAP;
qbuf.length = base::size(planes);
for (size_t i = 0; i < input_buffer_num_planes_; i++) {
// sets this to 0 means the size of the plane.
planes[i].bytesused = 0;
}
qbuf.m.planes = planes;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf);
input_record.at_device = true;
running_job_queue_.push(std::move(job_record));
free_input_buffers_.pop_back();
DVLOGF(3) << "enqueued frame id=" << job_record->task_id << " to device.";
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstance::EnqueueOutputRecord() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
DCHECK(!free_output_buffers_.empty());
// Enqueue an output (VIDEO_CAPTURE) buffer.
const int index = free_output_buffers_.back();
JpegBufferRecord& output_record = output_buffer_map_[index];
DCHECK(!output_record.at_device);
struct v4l2_buffer qbuf;
struct v4l2_plane planes[kMaxJpegPlane];
memset(&qbuf, 0, sizeof(qbuf));
memset(planes, 0, sizeof(planes));
qbuf.index = index;
qbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
qbuf.memory = V4L2_MEMORY_MMAP;
qbuf.length = base::size(planes);
qbuf.m.planes = planes;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf);
output_record.at_device = true;
free_output_buffers_.pop_back();
return true;
}
size_t V4L2JpegEncodeAccelerator::EncodedInstance::FinalizeJpegImage(
uint8_t* dst_ptr,
const JpegBufferRecord& output_buffer,
size_t buffer_size,
std::unique_ptr<UnalignedSharedMemory> exif_shm) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
size_t idx;
// Fill SOI and EXIF markers.
dst_ptr[0] = 0xFF;
dst_ptr[1] = JPEG_SOI;
idx = 2;
if (exif_shm) {
uint8_t* exif_buffer = static_cast<uint8_t*>(exif_shm->memory());
size_t exif_buffer_size = exif_shm->size();
// Application Segment for Exif data.
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)};
memcpy(dst_ptr + idx, kAppSegment, sizeof(kAppSegment));
idx += sizeof(kAppSegment);
memcpy(dst_ptr + idx, exif_buffer, exif_buffer_size);
idx += exif_buffer_size;
} else {
// Application Segment - JFIF standard 1.01.
static const uint8_t kAppSegment[] = {
0xFF, JPEG_APP0, 0x00,
0x10, // Segment length:16 (2-byte).
0x4A, // J
0x46, // F
0x49, // I
0x46, // F
0x00, // 0
0x01, // Major version.
0x01, // Minor version.
0x01, // Density units 0:no units, 1:pixels per inch,
// 2: pixels per cm.
0x00,
0x48, // X density (2-byte).
0x00,
0x48, // Y density (2-byte).
0x00, // Thumbnail width.
0x00 // Thumbnail height.
};
memcpy(dst_ptr + idx, kAppSegment, sizeof(kAppSegment));
idx += sizeof(kAppSegment);
}
switch (output_buffer_pixelformat_) {
case V4L2_PIX_FMT_JPEG_RAW:
// Fill the other jpeg markers for RAW JPEG.
memcpy(dst_ptr + idx, jpeg_markers_.data(), jpeg_markers_.size());
idx += jpeg_markers_.size();
// Fill Compressed stream.
memcpy(dst_ptr + idx, output_buffer.address[0], buffer_size);
idx += buffer_size;
// Fill EOI. Before Fill EOI we checked if the V4L2 device filled EOI
// first.
if (dst_ptr[idx - 2] != 0xFF && dst_ptr[idx - 1] != JPEG_EOI) {
dst_ptr[idx] = 0xFF;
dst_ptr[idx + 1] = JPEG_EOI;
idx += 2;
}
break;
default:
NOTREACHED() << "Unsupported output pixel format";
}
return idx;
}
void V4L2JpegEncodeAccelerator::EncodedInstance::Dequeue() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
// Dequeue completed input (VIDEO_OUTPUT) buffers,
// and recycle to the free list.
struct v4l2_buffer dqbuf;
struct v4l2_plane planes[kMaxI420Plane];
while (InputBufferQueuedCount() > 0) {
DCHECK(input_streamon_);
memset(&dqbuf, 0, sizeof(dqbuf));
memset(planes, 0, sizeof(planes));
dqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
dqbuf.memory = V4L2_MEMORY_MMAP;
dqbuf.length = base::size(planes);
dqbuf.m.planes = planes;
if (device_->Ioctl(VIDIOC_DQBUF, &dqbuf) != 0) {
if (errno == EAGAIN) {
// EAGAIN if we're just out of buffers to dequeue.
break;
}
VPLOGF(1) << "ioctl() failed: input buffer VIDIOC_DQBUF failed.";
NotifyError(kInvalidBitstreamBufferId, PLATFORM_FAILURE);
return;
}
I420BufferRecord& input_record = input_buffer_map_[dqbuf.index];
DCHECK(input_record.at_device);
input_record.at_device = false;
free_input_buffers_.push_back(dqbuf.index);
if (dqbuf.flags & V4L2_BUF_FLAG_ERROR) {
VLOGF(1) << "Error in dequeued input buffer.";
NotifyError(kInvalidBitstreamBufferId, PARSE_IMAGE_FAILED);
running_job_queue_.pop();
}
}
// Dequeue completed output (VIDEO_CAPTURE) buffers, recycle to the free list.
// Return the finished buffer to the client via the job ready callback.
// If dequeued input buffer has an error, the error frame has removed from
// |running_job_queue_|. We only have to dequeue output buffer when we
// actually have pending frames in |running_job_queue_| and also enqueued
// output buffers.
while (!running_job_queue_.empty() && OutputBufferQueuedCount() > 0) {
DCHECK(output_streamon_);
memset(&dqbuf, 0, sizeof(dqbuf));
memset(planes, 0, sizeof(planes));
dqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
dqbuf.memory = V4L2_MEMORY_MMAP;
dqbuf.length = base::size(planes);
dqbuf.m.planes = planes;
if (device_->Ioctl(VIDIOC_DQBUF, &dqbuf) != 0) {
if (errno == EAGAIN) {
// EAGAIN if we're just out of buffers to dequeue.
break;
}
VPLOGF(1) << "ioctl() failed: output buffer VIDIOC_DQBUF failed.";
NotifyError(kInvalidBitstreamBufferId, PLATFORM_FAILURE);
return;
}
JpegBufferRecord& output_record = output_buffer_map_[dqbuf.index];
DCHECK(output_record.at_device);
output_record.at_device = false;
free_output_buffers_.push_back(dqbuf.index);
// Jobs are always processed in FIFO order.
std::unique_ptr<JobRecord> job_record =
std::move(running_job_queue_.front());
running_job_queue_.pop();
if (dqbuf.flags & V4L2_BUF_FLAG_ERROR) {
VLOGF(1) << "Error in dequeued output buffer.";
NotifyError(kInvalidBitstreamBufferId, PARSE_IMAGE_FAILED);
return;
}
size_t jpeg_size = FinalizeJpegImage(
static_cast<uint8_t*>(job_record->output_shm.memory()), output_record,
planes[0].bytesused, std::move(job_record->exif_shm));
if (!jpeg_size) {
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
DVLOGF(4) << "Encoding finished, returning bitstream buffer, id="
<< job_record->task_id;
parent_->VideoFrameReady(job_record->task_id, jpeg_size);
}
}
void V4L2JpegEncodeAccelerator::EncodedInstance::NotifyError(int32_t task_id,
Status status) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
parent_->NotifyError(task_id, status);
}
V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::EncodedInstanceDmaBuf(
V4L2JpegEncodeAccelerator* parent)
: parent_(parent),
input_streamon_(false),
output_streamon_(false),
input_buffer_pixelformat_(0),
input_buffer_num_planes_(0),
output_buffer_pixelformat_(0) {}
V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::~EncodedInstanceDmaBuf() {}
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::DestroyTask() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
while (!input_job_queue_.empty())
input_job_queue_.pop();
while (!running_job_queue_.empty())
running_job_queue_.pop();
DestroyInputBuffers();
DestroyOutputBuffers();
}
bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::Initialize() {
device_ = V4L2Device::Create();
gpu_memory_buffer_support_ = std::make_unique<gpu::GpuMemoryBufferSupport>();
if (!device_) {
VLOGF(1) << "Failed to Create V4L2Device";
return false;
}
// We prefer V4L2_PIX_FMT_JPEG because V4L2_PIX_FMT_JPEG_RAW was rejected
// upstream.
output_buffer_pixelformat_ = V4L2_PIX_FMT_JPEG;
if (!device_->Open(V4L2Device::Type::kJpegEncoder,
output_buffer_pixelformat_)) {
output_buffer_pixelformat_ = V4L2_PIX_FMT_JPEG_RAW;
if (!device_->Open(V4L2Device::Type::kJpegEncoder,
output_buffer_pixelformat_)) {
VLOGF(1) << "Failed to open device";
return false;
}
}
// Capabilities check.
struct v4l2_capability caps;
const __u32 kCapsRequired = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
memset(&caps, 0, sizeof(caps));
if (device_->Ioctl(VIDIOC_QUERYCAP, &caps) != 0) {
VPLOGF(1) << "ioctl() failed: VIDIOC_QUERYCAP";
return false;
}
if ((caps.capabilities & kCapsRequired) != kCapsRequired) {
VLOGF(1) << "VIDIOC_QUERYCAP, caps check failed: 0x" << std::hex
<< caps.capabilities;
return false;
}
return true;
}
// static
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::FillQuantizationTable(
int quality,
const uint8_t* basic_table,
uint8_t* dst_table) {
unsigned int temp;
if (quality < 50)
quality = 5000 / quality;
else
quality = 200 - quality * 2;
for (size_t i = 0; i < kDctSize; i++) {
temp = ((unsigned int)basic_table[kZigZag8x8[i]] * quality + 50) / 100;
/* limit the values to the valid range */
dst_table[i] = base::clamp(temp, 1u, 255u);
}
}
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::PrepareJpegMarkers(
gfx::Size coded_size) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
// Quantization Tables.
// i = 0 for Luminance
// i = 1 for Chrominance
const int kNumDQT = 2;
for (size_t i = 0; i < kNumDQT; ++i) {
const uint8_t kQuantSegment[] = {
0xFF, JPEG_DQT, 0x00,
0x03 + kDctSize, // Segment length:67 (2-byte).
static_cast<uint8_t>(i) // Precision (4-bit high) = 0,
// Index (4-bit low) = i.
};
for (size_t j = 0; j < sizeof(kQuantSegment); ++j) {
jpeg_markers_.push_back(kQuantSegment[j]);
}
for (size_t j = 0; j < kDctSize; ++j) {
jpeg_markers_.push_back(quantization_table_[i].value[j]);
}
}
// Start of Frame - Baseline.
const int kNumOfComponents = 3;
const uint8_t kStartOfFrame[] = {
0xFF,
JPEG_SOF0, // Baseline.
0x00,
0x11, // Segment length:17 (2-byte).
8, // Data precision.
static_cast<uint8_t>((coded_size.height() >> 8) & 0xFF),
static_cast<uint8_t>(coded_size.height() & 0xFF),
static_cast<uint8_t>((coded_size.width() >> 8) & 0xFF),
static_cast<uint8_t>(coded_size.width() & 0xFF),
kNumOfComponents,
};
for (size_t i = 0; i < sizeof(kStartOfFrame); ++i) {
jpeg_markers_.push_back(kStartOfFrame[i]);
}
// i = 0 for Y Plane
// i = 1 for U Plane
// i = 2 for V Plane
for (size_t i = 0; i < kNumOfComponents; ++i) {
// These are the values for U and V planes.
uint8_t h_sample_factor = 1;
uint8_t v_sample_factor = 1;
uint8_t quant_table_number = 1;
if (!i) {
// These are the values for Y plane.
h_sample_factor = 2;
v_sample_factor = 2;
quant_table_number = 0;
}
jpeg_markers_.push_back(i + 1);
// Horizontal Sample Factor (4-bit high),
// Vertical Sample Factor (4-bit low).
jpeg_markers_.push_back((h_sample_factor << 4) | v_sample_factor);
jpeg_markers_.push_back(quant_table_number);
}
// Huffman Tables.
static const uint8_t kDcSegment[] = {
0xFF, JPEG_DHT, 0x00,
0x1F, // Segment length:31 (2-byte).
};
static const uint8_t kAcSegment[] = {
0xFF, JPEG_DHT, 0x00,
0xB5, // Segment length:181 (2-byte).
};
// i = 0 for Luminance
// i = 1 for Chrominance
const int kNumHuffmanTables = 2;
for (size_t i = 0; i < kNumHuffmanTables; ++i) {
// DC Table.
for (size_t j = 0; j < sizeof(kDcSegment); ++j) {
jpeg_markers_.push_back(kDcSegment[j]);
}
// Type (4-bit high) = 0:DC, Index (4-bit low).
jpeg_markers_.push_back(static_cast<uint8_t>(i));
const JpegHuffmanTable& dcTable = kDefaultDcTable[i];
for (size_t j = 0; j < kNumDcRunSizeBits; ++j)
jpeg_markers_.push_back(dcTable.code_length[j]);
for (size_t j = 0; j < kNumDcCodeWordsHuffVal; ++j)
jpeg_markers_.push_back(dcTable.code_value[j]);
// AC Table.
for (size_t j = 0; j < sizeof(kAcSegment); ++j) {
jpeg_markers_.push_back(kAcSegment[j]);
}
// Type (4-bit high) = 1:AC, Index (4-bit low).
jpeg_markers_.push_back(0x10 | static_cast<uint8_t>(i));
const JpegHuffmanTable& acTable = kDefaultAcTable[i];
for (size_t j = 0; j < kNumAcRunSizeBits; ++j)
jpeg_markers_.push_back(acTable.code_length[j]);
for (size_t j = 0; j < kNumAcCodeWordsHuffVal; ++j)
jpeg_markers_.push_back(acTable.code_value[j]);
}
// Start of Scan.
static const uint8_t kStartOfScan[] = {
0xFF, JPEG_SOS, 0x00,
0x0C, // Segment Length:12 (2-byte).
0x03 // Number of components in scan.
};
for (size_t i = 0; i < sizeof(kStartOfScan); ++i) {
jpeg_markers_.push_back(kStartOfScan[i]);
}
// i = 0 for Y Plane
// i = 1 for U Plane
// i = 2 for V Plane
for (uint8_t i = 0; i < kNumOfComponents; ++i) {
uint8_t dc_table_number = 1;
uint8_t ac_table_number = 1;
if (!i) {
dc_table_number = 0;
ac_table_number = 0;
}
jpeg_markers_.push_back(i + 1);
// DC Table Selector (4-bit high), AC Table Selector (4-bit low).
jpeg_markers_.push_back((dc_table_number << 4) | ac_table_number);
}
jpeg_markers_.push_back(0x00); // 0 for Baseline.
jpeg_markers_.push_back(0x3F); // 63 for Baseline.
jpeg_markers_.push_back(0x00); // 0 for Baseline.
}
bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::SetUpJpegParameters(
int quality,
gfx::Size coded_size) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
struct v4l2_ext_controls ctrls;
struct v4l2_ext_control ctrl;
struct v4l2_query_ext_ctrl queryctrl;
memset(&ctrls, 0, sizeof(ctrls));
memset(&ctrl, 0, sizeof(ctrl));
memset(&queryctrl, 0, sizeof(queryctrl));
ctrls.ctrl_class = V4L2_CTRL_CLASS_JPEG;
ctrls.controls = &ctrl;
ctrls.count = 1;
switch (output_buffer_pixelformat_) {
case V4L2_PIX_FMT_JPEG_RAW:
FillQuantizationTable(quality, kDefaultQuantTable[0].value,
quantization_table_[0].value);
FillQuantizationTable(quality, kDefaultQuantTable[1].value,
quantization_table_[1].value);
ctrl.id = V4L2_CID_JPEG_LUMA_QUANTIZATION;
ctrl.size = kDctSize;
ctrl.ptr = quantization_table_[0].value;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_EXT_CTRLS, &ctrls);
ctrl.id = V4L2_CID_JPEG_CHROMA_QUANTIZATION;
ctrl.size = kDctSize;
ctrl.ptr = quantization_table_[1].value;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_EXT_CTRLS, &ctrls);
// We need to prepare our own JPEG Markers.
PrepareJpegMarkers(coded_size);
break;
case V4L2_PIX_FMT_JPEG:
queryctrl.id = V4L2_CID_JPEG_COMPRESSION_QUALITY;
queryctrl.type = V4L2_CTRL_TYPE_INTEGER;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QUERY_EXT_CTRL, &queryctrl);
// interpolate the quality value
// Map quality value from range 1-100 to min-max.
quality = queryctrl.minimum +
(quality - 1) * (queryctrl.maximum - queryctrl.minimum) / 99;
ctrl.id = V4L2_CID_JPEG_COMPRESSION_QUALITY;
ctrl.value = quality;
VLOG(1) << "JPEG Quality: max:" << queryctrl.maximum
<< ", min:" << queryctrl.minimum << ", value:" << quality;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_EXT_CTRLS, &ctrls);
break;
default:
NOTREACHED();
}
return true;
}
size_t
V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::InputBufferQueuedCount() {
return kBufferCount - free_input_buffers_.size();
}
size_t
V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::OutputBufferQueuedCount() {
return kBufferCount - free_output_buffers_.size();
}
bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::CreateBuffers(
gfx::Size coded_size,
const VideoFrameLayout& input_layout,
size_t output_buffer_size) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
// The order of set output/input formats matters.
// rk3399 reset input format when we set output format.
if (!SetOutputBufferFormat(coded_size, output_buffer_size)) {
return false;
}
if (!SetInputBufferFormat(coded_size, input_layout)) {
return false;
}
if (!RequestInputBuffers()) {
return false;
}
if (!RequestOutputBuffers()) {
return false;
}
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::SetInputBufferFormat(
gfx::Size coded_size,
const VideoFrameLayout& input_layout) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
DCHECK(!input_streamon_);
DCHECK(input_job_queue_.empty());
constexpr uint32_t input_pix_fmt_candidates[] = {V4L2_PIX_FMT_NV12M,
V4L2_PIX_FMT_NV12};
struct v4l2_format format;
input_buffer_pixelformat_ = 0;
for (const auto input_pix_fmt : input_pix_fmt_candidates) {
DCHECK_EQ(Fourcc::FromV4L2PixFmt(input_pix_fmt)->ToVideoPixelFormat(),
PIXEL_FORMAT_NV12);
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
format.fmt.pix_mp.num_planes = kMaxNV12Plane;
format.fmt.pix_mp.pixelformat = input_pix_fmt;
format.fmt.pix_mp.field = V4L2_FIELD_ANY;
// set the input buffer resolution with padding and use selection API to
// crop the coded size.
format.fmt.pix_mp.width = input_layout.planes()[0].stride;
format.fmt.pix_mp.height = coded_size.height();
auto num_planes = input_layout.num_planes();
for (size_t i = 0; i < num_planes; i++) {
format.fmt.pix_mp.plane_fmt[i].sizeimage = input_layout.planes()[i].size;
format.fmt.pix_mp.plane_fmt[i].bytesperline =
input_layout.planes()[i].stride;
}
if (device_->Ioctl(VIDIOC_S_FMT, &format) == 0 &&
format.fmt.pix_mp.pixelformat == input_pix_fmt) {
device_input_layout_ = V4L2Device::V4L2FormatToVideoFrameLayout(format);
// Save V4L2 returned values.
input_buffer_pixelformat_ = format.fmt.pix_mp.pixelformat;
input_buffer_num_planes_ = format.fmt.pix_mp.num_planes;
break;
}
}
if (input_buffer_pixelformat_ == 0) {
VLOGF(1) << "Neither NV12 nor NV12M is supported.";
return false;
}
// It can't allow different width.
if (format.fmt.pix_mp.width !=
static_cast<uint32_t>(input_layout.planes()[0].stride)) {
LOG(WARNING) << "Different stride:" << format.fmt.pix_mp.width
<< "!=" << input_layout.planes()[0].stride;
return false;
}
// We can allow our buffer to have larger height than encoder's requirement
// because we set the 2nd plane by data_offset now.
if (format.fmt.pix_mp.height > static_cast<uint32_t>(coded_size.height())) {
if (input_buffer_pixelformat_ == V4L2_PIX_FMT_NV12M) {
// Calculate the real buffer height of the DMA buffer from minigbm.
uint32_t height_with_padding =
input_layout.planes()[0].size / input_layout.planes()[0].stride;
if (format.fmt.pix_mp.height > height_with_padding) {
LOG(WARNING) << "Encoder requires larger height:"
<< format.fmt.pix_mp.height << ">" << height_with_padding;
return false;
}
} else {
LOG(WARNING) << "Encoder requires larger height:"
<< format.fmt.pix_mp.height << ">" << coded_size.height();
return false;
}
}
if ((uint32_t)coded_size.width() != format.fmt.pix_mp.width ||
(uint32_t)coded_size.height() != format.fmt.pix_mp.height) {
v4l2_selection selection = {};
selection.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
selection.target = V4L2_SEL_TGT_CROP;
selection.flags = V4L2_SEL_FLAG_GE | V4L2_SEL_FLAG_LE;
selection.r.left = 0;
selection.r.top = 0;
selection.r.width = coded_size.width();
selection.r.height = coded_size.height();
if (device_->Ioctl(VIDIOC_S_SELECTION, &selection) != 0) {
LOG(WARNING) << "VIDIOC_S_SELECTION Fail";
return false;
}
}
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::SetOutputBufferFormat(
gfx::Size coded_size,
size_t buffer_size) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
DCHECK(!output_streamon_);
DCHECK(running_job_queue_.empty());
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
format.fmt.pix_mp.num_planes = kMaxJpegPlane;
format.fmt.pix_mp.pixelformat = output_buffer_pixelformat_;
format.fmt.pix_mp.field = V4L2_FIELD_ANY;
format.fmt.pix_mp.plane_fmt[0].sizeimage = buffer_size;
format.fmt.pix_mp.width = coded_size.width();
format.fmt.pix_mp.height = coded_size.height();
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format);
DCHECK_EQ(format.fmt.pix_mp.pixelformat, output_buffer_pixelformat_);
output_buffer_sizeimage_ = format.fmt.pix_mp.plane_fmt[0].sizeimage;
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::RequestInputBuffers() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
format.fmt.pix_mp.pixelformat = input_buffer_pixelformat_;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_G_FMT, &format);
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));
reqbufs.count = kBufferCount;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
reqbufs.memory = V4L2_MEMORY_DMABUF;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs);
DCHECK(free_input_buffers_.empty());
for (size_t i = 0; i < reqbufs.count; ++i) {
free_input_buffers_.push_back(i);
}
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::RequestOutputBuffers() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));
reqbufs.count = kBufferCount;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
reqbufs.memory = V4L2_MEMORY_DMABUF;
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs);
DCHECK(free_output_buffers_.empty());
for (size_t i = 0; i < reqbufs.count; ++i) {
free_output_buffers_.push_back(i);
}
return true;
}
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::DestroyInputBuffers() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
free_input_buffers_.clear();
if (input_streamon_) {
__u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMOFF, &type);
input_streamon_ = false;
}
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));
reqbufs.count = 0;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
reqbufs.memory = V4L2_MEMORY_DMABUF;
IOCTL_OR_LOG_ERROR(VIDIOC_REQBUFS, &reqbufs);
input_buffer_num_planes_ = 0;
}
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::DestroyOutputBuffers() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
free_output_buffers_.clear();
if (output_streamon_) {
__u32 type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMOFF, &type);
output_streamon_ = false;
}
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));
reqbufs.count = 0;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
reqbufs.memory = V4L2_MEMORY_DMABUF;
IOCTL_OR_LOG_ERROR(VIDIOC_REQBUFS, &reqbufs);
}
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::ServiceDevice() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
if (!running_job_queue_.empty()) {
Dequeue();
}
EnqueueInput();
EnqueueOutput();
DVLOGF(3) << "buffer counts: INPUT[" << input_job_queue_.size()
<< "] => DEVICE[" << free_input_buffers_.size() << "/"
<< "->" << free_output_buffers_.size() << "]";
}
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::EnqueueInput() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
while (!input_job_queue_.empty() && !free_input_buffers_.empty()) {
if (!EnqueueInputRecord())
return;
}
if (!input_streamon_ && InputBufferQueuedCount()) {
__u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMON, &type);
input_streamon_ = true;
}
}
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::EnqueueOutput() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
while (running_job_queue_.size() > OutputBufferQueuedCount() &&
!free_output_buffers_.empty()) {
if (!EnqueueOutputRecord())
return;
}
if (!output_streamon_ && OutputBufferQueuedCount()) {
__u32 type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMON, &type);
output_streamon_ = true;
}
}
bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::EnqueueInputRecord() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
DCHECK(!input_job_queue_.empty());
DCHECK(!free_input_buffers_.empty());
// Enqueue an input (VIDEO_OUTPUT) buffer for an input video frame.
std::unique_ptr<JobRecord> job_record = std::move(input_job_queue_.front());
input_job_queue_.pop();
const int index = free_input_buffers_.back();
struct v4l2_buffer qbuf;
struct v4l2_plane planes[kMaxNV12Plane];
memset(&qbuf, 0, sizeof(qbuf));
memset(planes, 0, sizeof(planes));
qbuf.index = index;
qbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
qbuf.memory = V4L2_MEMORY_DMABUF;
qbuf.length = base::size(planes);
qbuf.m.planes = planes;
const auto& frame = job_record->input_frame;
for (size_t i = 0; i < input_buffer_num_planes_; i++) {
if (device_input_layout_->is_multi_planar()) {
qbuf.m.planes[i].bytesused = base::checked_cast<__u32>(
VideoFrame::PlaneSize(frame->format(), i,
device_input_layout_->coded_size())
.GetArea());
} else {
qbuf.m.planes[i].bytesused = VideoFrame::AllocationSize(
frame->format(), device_input_layout_->coded_size());
}
const auto& fds = frame->DmabufFds();
const auto& layout_planes = frame->layout().planes();
qbuf.m.planes[i].m.fd = (i < fds.size()) ? fds[i].get() : fds.back().get();
qbuf.m.planes[i].data_offset = layout_planes[i].offset;
qbuf.m.planes[i].bytesused += qbuf.m.planes[i].data_offset;
qbuf.m.planes[i].length =
layout_planes[i].size + qbuf.m.planes[i].data_offset;
}
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf);
running_job_queue_.push(std::move(job_record));
free_input_buffers_.pop_back();
return true;
}
bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::EnqueueOutputRecord() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
DCHECK(!free_output_buffers_.empty());
// Enqueue an output (VIDEO_CAPTURE) buffer.
const int index = free_output_buffers_.back();
struct v4l2_buffer qbuf;
struct v4l2_plane planes[kMaxJpegPlane];
memset(&qbuf, 0, sizeof(qbuf));
memset(planes, 0, sizeof(planes));
qbuf.index = index;
qbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
qbuf.memory = V4L2_MEMORY_DMABUF;
qbuf.length = base::size(planes);
qbuf.m.planes = planes;
auto& job_record = running_job_queue_.back();
for (size_t i = 0; i < qbuf.length; i++) {
planes[i].m.fd = job_record->output_frame->DmabufFds()[i].get();
}
IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf);
free_output_buffers_.pop_back();
return true;
}
size_t V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::FinalizeJpegImage(
scoped_refptr<VideoFrame> output_frame,
size_t buffer_size,
std::unique_ptr<UnalignedSharedMemory> exif_shm) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
size_t idx = 0;
auto output_gmb_handle = CreateGpuMemoryBufferHandle(output_frame.get());
DCHECK(!output_gmb_handle.is_null());
// In this case, we use the R_8 buffer with height == 1 to represent a data
// container. As a result, we use plane.stride as size of the data here since
// plane.size might be larger due to height alignment.
const gfx::Size output_gmb_buffer_size(
base::checked_cast<int32_t>(output_frame->layout().planes()[0].stride),
1);
auto output_gmb_buffer =
gpu_memory_buffer_support_->CreateGpuMemoryBufferImplFromHandle(
std::move(output_gmb_handle), output_gmb_buffer_size,
gfx::BufferFormat::R_8, gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE,
base::DoNothing());
bool isMapped = output_gmb_buffer->Map();
if (!isMapped) {
VLOGF(1) << "Failed to map gmb buffer";
return 0;
}
uint8_t* dst_ptr = static_cast<uint8_t*>(output_gmb_buffer->memory(0));
// Fill SOI and EXIF markers.
static const uint8_t kJpegStart[] = {0xFF, JPEG_SOI};
if (exif_shm) {
uint8_t* exif_buffer = static_cast<uint8_t*>(exif_shm->memory());
size_t exif_buffer_size = exif_shm->size();
// Application Segment for Exif data.
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)};
if (output_buffer_pixelformat_ == V4L2_PIX_FMT_JPEG_RAW) {
// Move compressed data first.
size_t compressed_data_offset = sizeof(kJpegStart) + sizeof(kAppSegment) +
exif_buffer_size + jpeg_markers_.size();
if (buffer_size + compressed_data_offset > output_buffer_sizeimage_) {
LOG(WARNING) << "JPEG buffer is too small for the EXIF metadata";
return 0;
}
memmove(dst_ptr + compressed_data_offset, dst_ptr, buffer_size);
} else if (output_buffer_pixelformat_ == V4L2_PIX_FMT_JPEG) {
// Move data after SOI and APP0 marker for exif room.
// The JPEG from V4L2_PIX_FMT_JPEG is
// SOI-APP0-DQT-marker1-marker2-...-markerN-compressed stream-EOI
// |......| <- src_data_offset = len(SOI) + len(APP0)
// |...................| <- data_offset = len(SOI) + len(APP1)
size_t data_offset =
sizeof(kJpegStart) + sizeof(kAppSegment) + exif_buffer_size;
size_t app0_length = 2 + ((dst_ptr[4] << 16) | dst_ptr[5]);
size_t src_data_offset = sizeof(kJpegStart) + app0_length;
buffer_size -= src_data_offset;
if (buffer_size + data_offset > output_buffer_sizeimage_) {
LOG(WARNING) << "JPEG buffer is too small for the EXIF metadata";
return 0;
}
memmove(dst_ptr + data_offset, dst_ptr + src_data_offset, buffer_size);
}
memcpy(dst_ptr, kJpegStart, sizeof(kJpegStart));
idx += sizeof(kJpegStart);
memcpy(dst_ptr + idx, kAppSegment, sizeof(kAppSegment));
idx += sizeof(kAppSegment);
memcpy(dst_ptr + idx, exif_buffer, exif_buffer_size);
idx += exif_buffer_size;
} else if (output_buffer_pixelformat_ == V4L2_PIX_FMT_JPEG_RAW) {
// For no exif_shm we don't need to do anything for V4L2_PIX_FMT_JPEG.
// So we only need to know if the format is V4L2_PIX_FMT_JPEG_RAW.
// Application Segment - JFIF standard 1.01.
static const uint8_t kAppSegment[] = {
0xFF, JPEG_APP0, 0x00,
0x10, // Segment length:16 (2-byte).
0x4A, // J
0x46, // F
0x49, // I
0x46, // F
0x00, // 0
0x01, // Major version.
0x01, // Minor version.
0x01, // Density units 0:no units, 1:pixels per inch,
// 2: pixels per cm.
0x00,
0x48, // X density (2-byte).
0x00,
0x48, // Y density (2-byte).
0x00, // Thumbnail width.
0x00 // Thumbnail height.
};
// Move compressed data first.
size_t compressed_data_offset =
sizeof(kJpegStart) + sizeof(kAppSegment) + jpeg_markers_.size();
memmove(dst_ptr + compressed_data_offset, dst_ptr, buffer_size);
memcpy(dst_ptr, kJpegStart, sizeof(kJpegStart));
idx += sizeof(kJpegStart);
memcpy(dst_ptr + idx, kAppSegment, sizeof(kAppSegment));
idx += sizeof(kAppSegment);
}
switch (output_buffer_pixelformat_) {
case V4L2_PIX_FMT_JPEG_RAW:
// Fill the other jpeg markers for RAW JPEG.
memcpy(dst_ptr + idx, jpeg_markers_.data(), jpeg_markers_.size());
idx += jpeg_markers_.size();
// We already moved the compressed data.
idx += buffer_size;
// Fill EOI. Before Fill EOI we checked if the V4L2 device filled EOI
// first.
if (dst_ptr[idx - 2] != 0xFF && dst_ptr[idx - 1] != JPEG_EOI) {
dst_ptr[idx] = 0xFF;
dst_ptr[idx + 1] = JPEG_EOI;
idx += 2;
}
break;
case V4L2_PIX_FMT_JPEG:
idx += buffer_size;
break;
default:
NOTREACHED() << "Unsupported output pixel format";
}
output_gmb_buffer->Unmap();
return idx;
}
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::Dequeue() {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
// Dequeue completed input (VIDEO_OUTPUT) buffers,
// and recycle to the free list.
struct v4l2_buffer dqbuf;
struct v4l2_plane planes[kMaxNV12Plane];
while (InputBufferQueuedCount() > 0) {
DCHECK(input_streamon_);
memset(&dqbuf, 0, sizeof(dqbuf));
memset(planes, 0, sizeof(planes));
dqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
dqbuf.memory = V4L2_MEMORY_DMABUF;
dqbuf.length = base::size(planes);
dqbuf.m.planes = planes;
if (device_->Ioctl(VIDIOC_DQBUF, &dqbuf) != 0) {
if (errno == EAGAIN) {
// EAGAIN if we're just out of buffers to dequeue.
break;
}
VPLOGF(1) << "ioctl() failed: input buffer VIDIOC_DQBUF failed.";
NotifyError(kInvalidBitstreamBufferId, PLATFORM_FAILURE);
return;
}
free_input_buffers_.push_back(dqbuf.index);
if (dqbuf.flags & V4L2_BUF_FLAG_ERROR) {
VLOGF(1) << "Error in dequeued input buffer.";
NotifyError(kInvalidBitstreamBufferId, PARSE_IMAGE_FAILED);
running_job_queue_.pop();
}
}
// Dequeue completed output (VIDEO_CAPTURE) buffers, recycle to the free list.
// Return the finished buffer to the client via the job ready callback.
// If dequeued input buffer has an error, the error frame has removed from
// |running_job_queue_|. We only have to dequeue output buffer when we
// actually have pending frames in |running_job_queue_| and also enqueued
// output buffers.
while (!running_job_queue_.empty() && OutputBufferQueuedCount() > 0) {
DCHECK(output_streamon_);
memset(&dqbuf, 0, sizeof(dqbuf));
memset(planes, 0, sizeof(planes));
dqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
dqbuf.memory = V4L2_MEMORY_DMABUF;
dqbuf.length = base::size(planes);
dqbuf.m.planes = planes;
if (device_->Ioctl(VIDIOC_DQBUF, &dqbuf) != 0) {
if (errno == EAGAIN) {
// EAGAIN if we're just out of buffers to dequeue.
break;
}
VPLOGF(1) << "ioctl() failed: output buffer VIDIOC_DQBUF failed.";
NotifyError(kInvalidBitstreamBufferId, PLATFORM_FAILURE);
return;
}
free_output_buffers_.push_back(dqbuf.index);
// Jobs are always processed in FIFO order.
std::unique_ptr<JobRecord> job_record =
std::move(running_job_queue_.front());
running_job_queue_.pop();
if (dqbuf.flags & V4L2_BUF_FLAG_ERROR) {
VLOGF(1) << "Error in dequeued output buffer.";
NotifyError(kInvalidBitstreamBufferId, PARSE_IMAGE_FAILED);
return;
}
size_t jpeg_size =
FinalizeJpegImage(job_record->output_frame, planes[0].bytesused,
std::move(job_record->exif_shm));
if (!jpeg_size) {
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
DVLOGF(4) << "Encoding finished, returning bitstream buffer, id="
<< job_record->task_id;
parent_->VideoFrameReady(job_record->task_id, jpeg_size);
}
}
void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::NotifyError(
int32_t task_id,
Status status) {
DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
parent_->NotifyError(task_id, status);
}
V4L2JpegEncodeAccelerator::V4L2JpegEncodeAccelerator(
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
: child_task_runner_(base::ThreadTaskRunnerHandle::Get()),
io_task_runner_(io_task_runner),
client_(nullptr),
encoder_thread_("V4L2JpegEncodeThread"),
weak_factory_(this) {
weak_ptr_ = weak_factory_.GetWeakPtr();
}
V4L2JpegEncodeAccelerator::~V4L2JpegEncodeAccelerator() {
DCHECK(child_task_runner_->BelongsToCurrentThread());
if (encoder_thread_.IsRunning()) {
encoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::DestroyTask,
base::Unretained(this)));
encoder_thread_.Stop();
}
weak_factory_.InvalidateWeakPtrs();
}
void V4L2JpegEncodeAccelerator::DestroyTask() {
DCHECK(encoder_task_runner_->BelongsToCurrentThread());
while (!encoded_instances_.empty()) {
encoded_instances_.front()->DestroyTask();
encoded_instances_.pop();
}
while (!encoded_instances_dma_buf_.empty()) {
encoded_instances_dma_buf_.front()->DestroyTask();
encoded_instances_dma_buf_.pop();
}
}
void V4L2JpegEncodeAccelerator::VideoFrameReady(int32_t task_id,
size_t encoded_picture_size) {
if (!child_task_runner_->BelongsToCurrentThread()) {
child_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::VideoFrameReady,
weak_ptr_, task_id, encoded_picture_size));
return;
}
VLOGF(1) << "Encoding finished task id=" << task_id
<< " Compressed size:" << encoded_picture_size;
client_->VideoFrameReady(task_id, encoded_picture_size);
}
void V4L2JpegEncodeAccelerator::NotifyError(int32_t task_id, Status status) {
if (!child_task_runner_->BelongsToCurrentThread()) {
child_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::NotifyError,
weak_ptr_, task_id, status));
return;
}
VLOGF(1) << "Notifying of error " << status << " for task id " << task_id;
client_->NotifyError(task_id, status);
}
void V4L2JpegEncodeAccelerator::InitializeOnTaskRunner(
chromeos_camera::JpegEncodeAccelerator::Client* client,
chromeos_camera::JpegEncodeAccelerator::InitCB init_cb) {
DCHECK(child_task_runner_->BelongsToCurrentThread());
std::unique_ptr<EncodedInstanceDmaBuf> encoded_device(
new EncodedInstanceDmaBuf(this));
// We just check if we can initialize device here.
if (!encoded_device->Initialize()) {
VLOGF(1) << "Failed to initialize device";
std::move(init_cb).Run(HW_JPEG_ENCODE_NOT_SUPPORTED);
return;
}
if (!encoder_thread_.Start()) {
VLOGF(1) << "encoder thread failed to start";
std::move(init_cb).Run(THREAD_CREATION_FAILED);
return;
}
client_ = client;
encoder_task_runner_ = encoder_thread_.task_runner();
VLOGF(2) << "V4L2JpegEncodeAccelerator initialized.";
std::move(init_cb).Run(ENCODE_OK);
return;
}
void V4L2JpegEncodeAccelerator::InitializeAsync(
chromeos_camera::JpegEncodeAccelerator::Client* client,
chromeos_camera::JpegEncodeAccelerator::InitCB init_cb) {
DCHECK(child_task_runner_->BelongsToCurrentThread());
child_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&V4L2JpegEncodeAccelerator::InitializeOnTaskRunner,
weak_ptr_, client, BindToCurrentLoop(std::move(init_cb))));
}
size_t V4L2JpegEncodeAccelerator::GetMaxCodedBufferSize(
const gfx::Size& picture_size) {
return picture_size.GetArea() * 3 / 2 + kJpegDefaultHeaderSize;
}
void V4L2JpegEncodeAccelerator::Encode(
scoped_refptr<media::VideoFrame> video_frame,
int quality,
BitstreamBuffer* exif_buffer,
BitstreamBuffer output_buffer) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
DVLOGF(4) << "task_id=" << output_buffer.id()
<< ", size=" << output_buffer.size();
if (quality <= 0 || quality > 100) {
VLOGF(1) << "quality is not in range. " << quality;
NotifyError(output_buffer.id(), INVALID_ARGUMENT);
return;
}
if (video_frame->format() != VideoPixelFormat::PIXEL_FORMAT_I420) {
VLOGF(1) << "Format is not I420";
NotifyError(output_buffer.id(), INVALID_ARGUMENT);
return;
}
if (exif_buffer) {
VLOGF(4) << "EXIF size " << exif_buffer->size();
if (exif_buffer->size() > kMaxMarkerSizeAllowed) {
NotifyError(output_buffer.id(), INVALID_ARGUMENT);
return;
}
}
std::unique_ptr<JobRecord> job_record(new JobRecord(
video_frame, quality, exif_buffer, std::move(output_buffer)));
encoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::EncodeTaskLegacy,
base::Unretained(this), std::move(job_record)));
}
void V4L2JpegEncodeAccelerator::EncodeWithDmaBuf(
scoped_refptr<VideoFrame> input_frame,
scoped_refptr<VideoFrame> output_frame,
int quality,
int32_t task_id,
BitstreamBuffer* exif_buffer) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
if (quality <= 0 || quality > 100) {
VLOGF(1) << "quality is not in range. " << quality;
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
if (input_frame->format() != VideoPixelFormat::PIXEL_FORMAT_NV12) {
VLOGF(1) << "Format is not NV12";
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
if (exif_buffer) {
VLOGF(4) << "EXIF size " << exif_buffer->size();
if (exif_buffer->size() > kMaxMarkerSizeAllowed) {
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
}
std::unique_ptr<JobRecord> job_record(
new JobRecord(input_frame, output_frame, quality, task_id, exif_buffer));
encoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::EncodeTask,
base::Unretained(this), std::move(job_record)));
}
void V4L2JpegEncodeAccelerator::EncodeTaskLegacy(
std::unique_ptr<JobRecord> job_record) {
DCHECK(encoder_task_runner_->BelongsToCurrentThread());
if (!job_record->output_shm.MapAt(job_record->output_offset,
job_record->output_shm.size())) {
VPLOGF(1) << "could not map I420 bitstream_buffer";
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
if (job_record->exif_shm &&
!job_record->exif_shm->MapAt(job_record->exif_offset,
job_record->exif_shm->size())) {
VPLOGF(1) << "could not map exif bitstream_buffer";
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
// Check if the parameters of input frame changes.
// If it changes, we open a new device and put the job in it.
// If it doesn't change, we use the same device as last used.
gfx::Size coded_size = job_record->input_frame->coded_size();
if (latest_input_buffer_coded_size_legacy_ != coded_size ||
latest_quality_legacy_ != job_record->quality) {
std::unique_ptr<EncodedInstance> encoded_device(new EncodedInstance(this));
VLOGF(1) << "Open Device for quality " << job_record->quality
<< ", width: " << coded_size.width()
<< ", height: " << coded_size.height();
if (!encoded_device->Initialize()) {
VLOGF(1) << "Failed to initialize device";
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
if (!encoded_device->SetUpJpegParameters(job_record->quality, coded_size)) {
VLOGF(1) << "SetUpJpegParameters failed";
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
if (!encoded_device->CreateBuffers(coded_size,
job_record->output_shm.size())) {
VLOGF(1) << "Create buffers failed.";
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
latest_input_buffer_coded_size_legacy_ = coded_size;
latest_quality_legacy_ = job_record->quality;
encoded_instances_.push(std::move(encoded_device));
}
// Always use latest opened device for new job.
encoded_instances_.back()->input_job_queue_.push(std::move(job_record));
ServiceDeviceTaskLegacy();
}
void V4L2JpegEncodeAccelerator::EncodeTask(
std::unique_ptr<JobRecord> job_record) {
DCHECK(encoder_task_runner_->BelongsToCurrentThread());
if (job_record->exif_shm &&
!job_record->exif_shm->MapAt(job_record->exif_offset,
job_record->exif_shm->size())) {
VPLOGF(1) << "could not map exif bitstream_buffer";
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
// Check if the parameters of input frame changes.
// If it changes, we open a new device and put the job in it.
// If it doesn't change, we use the same device as last used.
gfx::Size coded_size = job_record->input_frame->coded_size();
if (latest_input_buffer_coded_size_ != coded_size ||
latest_quality_ != job_record->quality) {
std::unique_ptr<EncodedInstanceDmaBuf> encoded_device(
new EncodedInstanceDmaBuf(this));
VLOGF(1) << "Open Device for quality " << job_record->quality
<< ", width: " << coded_size.width()
<< ", height: " << coded_size.height();
if (!encoded_device->Initialize()) {
VLOGF(1) << "Failed to initialize device";
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
if (!encoded_device->SetUpJpegParameters(job_record->quality, coded_size)) {
VLOGF(1) << "SetUpJpegParameters failed";
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
// The output buffer size is coded in the first plane's size.
if (!encoded_device->CreateBuffers(
coded_size, job_record->input_frame->layout(),
job_record->output_frame->layout().planes()[0].size)) {
VLOGF(1) << "Create buffers failed.";
NotifyError(job_record->task_id, PLATFORM_FAILURE);
return;
}
latest_input_buffer_coded_size_ = coded_size;
latest_quality_ = job_record->quality;
encoded_instances_dma_buf_.push(std::move(encoded_device));
}
// Always use latest opened device for new job.
encoded_instances_dma_buf_.back()->input_job_queue_.push(
std::move(job_record));
ServiceDeviceTask();
}
void V4L2JpegEncodeAccelerator::ServiceDeviceTaskLegacy() {
DCHECK(encoder_task_runner_->BelongsToCurrentThread());
// Always service the first device to keep the input order.
encoded_instances_.front()->ServiceDevice();
// If we have more than 1 devices, we can remove the oldest one after all jobs
// finished.
if (encoded_instances_.size() > 1) {
if (encoded_instances_.front()->running_job_queue_.empty() &&
encoded_instances_.front()->input_job_queue_.empty()) {
encoded_instances_.pop();
}
}
if (!encoded_instances_.front()->running_job_queue_.empty() ||
!encoded_instances_.front()->input_job_queue_.empty()) {
encoder_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&V4L2JpegEncodeAccelerator::ServiceDeviceTaskLegacy,
base::Unretained(this)));
}
}
void V4L2JpegEncodeAccelerator::ServiceDeviceTask() {
DCHECK(encoder_task_runner_->BelongsToCurrentThread());
// Always service the first device to keep the input order.
encoded_instances_dma_buf_.front()->ServiceDevice();
// If we have more than 1 devices, we can remove the oldest one after all jobs
// finished.
if (encoded_instances_dma_buf_.size() > 1) {
if (encoded_instances_dma_buf_.front()->running_job_queue_.empty() &&
encoded_instances_dma_buf_.front()->input_job_queue_.empty()) {
encoded_instances_dma_buf_.pop();
}
}
if (!encoded_instances_dma_buf_.front()->running_job_queue_.empty() ||
!encoded_instances_dma_buf_.front()->input_job_queue_.empty()) {
encoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::ServiceDeviceTask,
base::Unretained(this)));
}
}
} // namespace media