| // 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& 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 = planes[i].offset; |
| qbuf.m.planes[i].bytesused += qbuf.m.planes[i].data_offset; |
| qbuf.m.planes[i].length = 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 |