| /* |
| * Copyright (C) 2013-2017 Intel Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Copyright 2018 The Chromium OS 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 "cros-camera/v4l2_device.h" |
| |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <linux/media.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| |
| #include "cros-camera/common.h" |
| |
| static inline bool IsValidV4L2BufferType(uint32_t type) { |
| return (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) || |
| (type == V4L2_BUF_TYPE_VIDEO_OUTPUT) || |
| (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) || |
| (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) || |
| (type == V4L2_BUF_TYPE_META_OUTPUT) || |
| (type == V4L2_BUF_TYPE_META_CAPTURE); |
| } |
| |
| namespace cros { |
| |
| #define V4L2_TYPE_IS_META(type) \ |
| ((type) == V4L2_BUF_TYPE_META_OUTPUT || (type) == V4L2_BUF_TYPE_META_CAPTURE) |
| |
| #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) |
| |
| V4L2Buffer::V4L2Buffer() : v4l2_buf_{} { |
| VLOGF_ENTER(); |
| v4l2_buf_.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
| planes_.resize(VIDEO_MAX_PLANES); |
| v4l2_buf_.m.planes = planes_.data(); |
| v4l2_buf_.length = planes_.size(); |
| } |
| |
| V4L2Buffer::V4L2Buffer(const V4L2Buffer& buf) : v4l2_buf_(buf.v4l2_buf_) { |
| VLOGF_ENTER(); |
| if (V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type)) { |
| planes_ = buf.planes_; |
| v4l2_buf_.m.planes = planes_.data(); |
| } |
| } |
| |
| void V4L2Buffer::SetType(uint32_t type) { |
| DCHECK(IsValidV4L2BufferType(type)); |
| |
| v4l2_buf_.type = type; |
| } |
| |
| uint32_t V4L2Buffer::Offset(uint32_t plane) const { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| return mp ? v4l2_buf_.m.planes[plane].m.mem_offset : v4l2_buf_.m.offset; |
| } |
| |
| void V4L2Buffer::SetOffset(uint32_t offset, uint32_t plane) { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| if (mp) |
| v4l2_buf_.m.planes[plane].m.mem_offset = offset; |
| else |
| v4l2_buf_.m.offset = offset; |
| } |
| |
| uint32_t V4L2Buffer::DataOffset(uint32_t plane) const { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| return mp ? v4l2_buf_.m.planes[plane].data_offset : 0; |
| } |
| |
| void V4L2Buffer::SetDataOffset(uint32_t offset, uint32_t plane) { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| if (mp) |
| v4l2_buf_.m.planes[plane].data_offset = offset; |
| } |
| |
| uintptr_t V4L2Buffer::Userptr(uint32_t plane) const { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| return mp ? v4l2_buf_.m.planes[plane].m.userptr : v4l2_buf_.m.userptr; |
| } |
| |
| void V4L2Buffer::SetUserptr(uintptr_t userptr, uint32_t plane) { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| if (mp) |
| v4l2_buf_.m.planes[plane].m.userptr = userptr; |
| else |
| v4l2_buf_.m.userptr = userptr; |
| } |
| |
| int V4L2Buffer::RequestFd() const { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| |
| return (v4l2_buf_.flags & V4L2_BUF_FLAG_REQUEST_FD) ? v4l2_buf_.request_fd |
| : -1; |
| } |
| |
| int V4L2Buffer::SetRequestFd(int fd) { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| |
| if (fd <= 0) { |
| return -EINVAL; |
| } |
| |
| v4l2_buf_.flags |= V4L2_BUF_FLAG_REQUEST_FD; |
| v4l2_buf_.request_fd = fd; |
| |
| return 0; |
| } |
| |
| int V4L2Buffer::ResetRequestFd() { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| |
| v4l2_buf_.flags &= ~V4L2_BUF_FLAG_REQUEST_FD; |
| v4l2_buf_.request_fd = 0; |
| |
| return 0; |
| } |
| |
| int V4L2Buffer::Fd(uint32_t plane) const { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| return mp ? v4l2_buf_.m.planes[plane].m.fd : v4l2_buf_.m.fd; |
| } |
| |
| void V4L2Buffer::SetFd(int fd, uint32_t plane) { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| if (mp) |
| v4l2_buf_.m.planes[plane].m.fd = fd; |
| else |
| v4l2_buf_.m.fd = fd; |
| } |
| |
| uint32_t V4L2Buffer::BytesUsed(uint32_t plane) const { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| return mp ? v4l2_buf_.m.planes[plane].bytesused : v4l2_buf_.bytesused; |
| } |
| |
| void V4L2Buffer::SetBytesUsed(uint32_t bytesused, uint32_t plane) { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| if (mp) |
| v4l2_buf_.m.planes[plane].bytesused = bytesused; |
| else |
| v4l2_buf_.bytesused = bytesused; |
| } |
| |
| uint32_t V4L2Buffer::Length(uint32_t plane) const { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| return mp ? v4l2_buf_.m.planes[plane].length : v4l2_buf_.length; |
| } |
| |
| void V4L2Buffer::SetLength(uint32_t length, uint32_t plane) { |
| DCHECK(IsValidV4L2BufferType(v4l2_buf_.type)); |
| bool mp = V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type); |
| DCHECK((!mp && plane == 0) || (mp && plane < planes_.size())); |
| |
| if (mp) |
| v4l2_buf_.m.planes[plane].length = length; |
| else |
| v4l2_buf_.length = length; |
| } |
| |
| const V4L2Buffer& V4L2Buffer::operator=(const V4L2Buffer& buf) { |
| v4l2_buf_ = buf.v4l2_buf_; |
| if (V4L2_TYPE_IS_MULTIPLANAR(v4l2_buf_.type)) { |
| planes_ = buf.planes_; |
| v4l2_buf_.m.planes = planes_.data(); |
| } |
| return *this; |
| } |
| |
| V4L2Format::V4L2Format() |
| : type_(0), |
| width_(0), |
| height_(0), |
| pixel_fmt_(0), |
| field_(V4L2_FIELD_NONE), |
| v4l2_fmt_() {} |
| |
| V4L2Format::V4L2Format(const v4l2_format& fmt) { |
| DCHECK(IsValidV4L2BufferType(fmt.type)); |
| type_ = fmt.type; |
| if (V4L2_TYPE_IS_META(fmt.type)) { |
| pixel_fmt_ = fmt.fmt.meta.dataformat; |
| plane_size_image_.push_back(fmt.fmt.meta.buffersize); |
| } else if (V4L2_TYPE_IS_MULTIPLANAR(fmt.type)) { |
| width_ = fmt.fmt.pix_mp.width; |
| height_ = fmt.fmt.pix_mp.height; |
| pixel_fmt_ = fmt.fmt.pix_mp.pixelformat; |
| field_ = fmt.fmt.pix_mp.field; |
| color_space_ = fmt.fmt.pix_mp.colorspace; |
| quantization_ = fmt.fmt.pix_mp.quantization; |
| for (uint8_t plane = 0; plane < fmt.fmt.pix_mp.num_planes; plane++) { |
| plane_bytes_per_line_.push_back( |
| fmt.fmt.pix_mp.plane_fmt[plane].bytesperline); |
| plane_size_image_.push_back(fmt.fmt.pix_mp.plane_fmt[plane].sizeimage); |
| } |
| } else { |
| width_ = fmt.fmt.pix.width; |
| height_ = fmt.fmt.pix.height; |
| pixel_fmt_ = fmt.fmt.pix.pixelformat; |
| field_ = fmt.fmt.pix.field; |
| color_space_ = fmt.fmt.pix.colorspace; |
| quantization_ = fmt.fmt.pix.quantization; |
| plane_bytes_per_line_.push_back(fmt.fmt.pix.bytesperline); |
| plane_size_image_.push_back(fmt.fmt.pix.sizeimage); |
| } |
| } |
| |
| void V4L2Format::SetType(uint32_t type) { |
| DCHECK(IsValidV4L2BufferType(type)); |
| type_ = type; |
| } |
| |
| uint32_t V4L2Format::Width() const { |
| return width_; |
| } |
| |
| void V4L2Format::SetWidth(uint32_t width) { |
| width_ = width; |
| } |
| |
| uint32_t V4L2Format::Height() const { |
| return height_; |
| } |
| |
| void V4L2Format::SetHeight(uint32_t height) { |
| height_ = height; |
| } |
| |
| uint32_t V4L2Format::PixelFormat() const { |
| return pixel_fmt_; |
| } |
| |
| void V4L2Format::SetPixelFormat(uint32_t format) { |
| pixel_fmt_ = format; |
| } |
| |
| uint32_t V4L2Format::Field() const { |
| return field_; |
| } |
| |
| void V4L2Format::SetField(uint32_t field) { |
| field_ = field; |
| } |
| |
| uint32_t V4L2Format::BytesPerLine(uint32_t plane) const { |
| DCHECK(plane < plane_bytes_per_line_.size()); |
| return plane_bytes_per_line_[plane]; |
| } |
| |
| void V4L2Format::SetBytesPerLine(uint32_t bytesperline, uint32_t plane) { |
| if (plane >= VIDEO_MAX_PLANES) { |
| LOGF(ERROR) << "Invalid plane " << plane; |
| return; |
| } |
| if (plane >= plane_bytes_per_line_.size()) { |
| plane_bytes_per_line_.resize(plane + 1); |
| } |
| plane_bytes_per_line_[plane] = bytesperline; |
| } |
| |
| uint32_t V4L2Format::SizeImage(uint32_t plane) const { |
| DCHECK(plane < plane_size_image_.size()); |
| return plane_size_image_[plane]; |
| } |
| |
| void V4L2Format::SetSizeImage(uint32_t size, uint32_t plane) { |
| if (plane >= VIDEO_MAX_PLANES) { |
| LOGF(ERROR) << "Invalid plane " << plane; |
| return; |
| } |
| if (plane >= plane_size_image_.size()) { |
| plane_size_image_.resize(plane + 1); |
| } |
| plane_size_image_[plane] = size; |
| } |
| |
| uint32_t V4L2Format::ColorSpace() const { |
| return color_space_; |
| } |
| |
| void V4L2Format::SetColorSpace(uint32_t profile) { |
| color_space_ = profile; |
| } |
| |
| uint32_t V4L2Format::Quantization() const { |
| return quantization_; |
| } |
| |
| void V4L2Format::SetQuantization(uint32_t quantization) { |
| quantization_ = quantization; |
| } |
| |
| v4l2_format* V4L2Format::Get() { |
| DCHECK(IsValidV4L2BufferType(type_)); |
| |
| v4l2_fmt_.type = type_; |
| if (V4L2_TYPE_IS_META(v4l2_fmt_.type)) { |
| DCHECK_EQ(plane_size_image_.size(), 1); |
| |
| v4l2_fmt_.fmt.meta.dataformat = pixel_fmt_; |
| v4l2_fmt_.fmt.meta.buffersize = plane_size_image_[0]; |
| } else if (V4L2_TYPE_IS_MULTIPLANAR(v4l2_fmt_.type)) { |
| // TODO(hywu): add DCHECK_EQ(plane_bytes_per_line_.size(), |
| // plane_size_image_.size()) |
| v4l2_fmt_.fmt.pix_mp.width = width_; |
| v4l2_fmt_.fmt.pix_mp.height = height_; |
| v4l2_fmt_.fmt.pix_mp.pixelformat = pixel_fmt_; |
| v4l2_fmt_.fmt.pix_mp.field = field_; |
| v4l2_fmt_.fmt.pix_mp.colorspace = color_space_; |
| v4l2_fmt_.fmt.pix_mp.quantization = quantization_; |
| v4l2_fmt_.fmt.pix_mp.num_planes = plane_bytes_per_line_.size(); |
| for (size_t plane = 0; plane < plane_bytes_per_line_.size(); plane++) { |
| v4l2_fmt_.fmt.pix_mp.plane_fmt[plane].bytesperline = |
| plane_bytes_per_line_[plane]; |
| } |
| for (size_t plane = 0; plane < plane_size_image_.size(); plane++) { |
| v4l2_fmt_.fmt.pix_mp.plane_fmt[plane].sizeimage = |
| plane_size_image_[plane]; |
| } |
| } else { |
| DCHECK_EQ(plane_bytes_per_line_.size(), 1); |
| DCHECK_EQ(plane_size_image_.size(), 1); |
| |
| v4l2_fmt_.fmt.pix.width = width_; |
| v4l2_fmt_.fmt.pix.height = height_; |
| v4l2_fmt_.fmt.pix.pixelformat = pixel_fmt_; |
| v4l2_fmt_.fmt.pix.field = field_; |
| v4l2_fmt_.fmt.pix.colorspace = color_space_; |
| v4l2_fmt_.fmt.pix.quantization = quantization_; |
| v4l2_fmt_.fmt.pix.bytesperline = plane_bytes_per_line_[0]; |
| v4l2_fmt_.fmt.pix.sizeimage = plane_size_image_[0]; |
| } |
| return &v4l2_fmt_; |
| } |
| |
| V4L2VideoNode::V4L2VideoNode(const std::string name) |
| : V4L2Device(name), state_(VideoNodeState::CLOSED) { |
| VLOGF_ENTER(); |
| } |
| |
| V4L2VideoNode::~V4L2VideoNode() { |
| VLOGF_ENTER(); |
| { |
| base::AutoLock l(state_lock_); |
| if (state_ == VideoNodeState::CLOSED) { |
| return; |
| } |
| } |
| Close(); |
| } |
| |
| int V4L2VideoNode::Open(int flags) { |
| VLOGF(1) << "Opening device " << name_; |
| base::AutoLock l(state_lock_); |
| int ret = V4L2Device::Open(flags); |
| if (ret != 0) { |
| LOGF(ERROR) << "Failed to open video device node " << name_; |
| return ret; |
| } |
| |
| struct v4l2_capability cap = {}; |
| ret = QueryCap(&cap); |
| if (ret != 0) { |
| LOGF(ERROR) << "Failed to query device " << name_ << " capabilities"; |
| V4L2Device::Close(); |
| return ret; |
| } |
| std::pair<uint32_t, enum v4l2_buf_type> buffer_type_mapper[] = { |
| {V4L2_CAP_VIDEO_CAPTURE, V4L2_BUF_TYPE_VIDEO_CAPTURE}, |
| {V4L2_CAP_VIDEO_CAPTURE_MPLANE, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE}, |
| {V4L2_CAP_VIDEO_OUTPUT, V4L2_BUF_TYPE_VIDEO_OUTPUT}, |
| {V4L2_CAP_VIDEO_OUTPUT_MPLANE, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE}, |
| {V4L2_CAP_META_CAPTURE, V4L2_BUF_TYPE_META_CAPTURE}, |
| {V4L2_CAP_META_OUTPUT, V4L2_BUF_TYPE_META_OUTPUT}}; |
| size_t i = 0; |
| for (; i < ARRAY_SIZE(buffer_type_mapper); i++) { |
| if (cap.capabilities & buffer_type_mapper[i].first) { |
| buffer_type_ = buffer_type_mapper[i].second; |
| break; |
| } |
| } |
| if (i == ARRAY_SIZE(buffer_type_mapper)) { |
| LOGF(ERROR) << "Unsupported device " << name_ << " capabilities 0x" |
| << std::hex << cap.capabilities; |
| V4L2Device::Close(); |
| return -EINVAL; |
| } |
| |
| state_ = VideoNodeState::OPEN; |
| return ret; |
| } |
| |
| int V4L2VideoNode::Close() { |
| VLOGF(1) << "Closing device " << name_; |
| base::AutoLock l(state_lock_); |
| if (state_ == VideoNodeState::STARTED || state_ == VideoNodeState::PREPARED) { |
| StopLocked(); |
| } |
| |
| int ret = V4L2Device::Close(); |
| state_ = (ret == 0) ? VideoNodeState::CLOSED : VideoNodeState::ERROR; |
| |
| return ret; |
| } |
| |
| enum v4l2_memory V4L2VideoNode::GetMemoryType() { |
| return memory_type_; |
| } |
| |
| enum v4l2_buf_type V4L2VideoNode::GetBufferType() { |
| return buffer_type_; |
| } |
| |
| int V4L2VideoNode::Stop(bool releaseBuffers) { |
| VLOGF(1) << "Stoping device " << name_; |
| base::AutoLock l(state_lock_); |
| if (state_ != VideoNodeState::STARTED && state_ != VideoNodeState::PREPARED) { |
| LOGF(WARNING) << "Trying to stop a device not started"; |
| return -EINVAL; |
| } |
| return StopLocked(releaseBuffers); |
| } |
| |
| int V4L2VideoNode::StopLocked(bool releaseBuffers) { |
| if (state_ == VideoNodeState::STARTED) { |
| // stream off |
| int ret = ::ioctl(fd_, VIDIOC_STREAMOFF, &buffer_type_); |
| if (ret < 0) { |
| PLOGF(ERROR) << name_ << " VIDIOC_STREAMOFF returned: " << ret; |
| return ret; |
| } |
| state_ = VideoNodeState::PREPARED; |
| } |
| |
| if (!releaseBuffers) { |
| return 0; |
| } |
| |
| if (state_ == VideoNodeState::PREPARED) { |
| RequestBuffers(0, memory_type_); |
| state_ = VideoNodeState::CONFIGURED; |
| } |
| |
| return 0; |
| } |
| |
| int V4L2VideoNode::Start() { |
| VLOGF(1) << "Starting device " << name_; |
| base::AutoLock l(state_lock_); |
| if (state_ != VideoNodeState::PREPARED) { |
| LOGF(ERROR) << "Invalid device state " << static_cast<int>(state_); |
| return -1; |
| } |
| |
| // stream on |
| int ret = ::ioctl(fd_, VIDIOC_STREAMON, &buffer_type_); |
| if (ret < 0) { |
| PLOGF(ERROR) << name_ << " VIDIOC_STREAMON returned: " << ret; |
| return ret; |
| } |
| |
| state_ = VideoNodeState::STARTED; |
| |
| return 0; |
| } |
| |
| int V4L2VideoNode::SetFormat(const V4L2Format& format) { |
| VLOGF_ENTER(); |
| base::AutoLock l(state_lock_); |
| if ((state_ != VideoNodeState::OPEN) && |
| (state_ != VideoNodeState::CONFIGURED) && |
| (state_ != VideoNodeState::PREPARED)) { |
| LOGF(ERROR) << "Invalid device state " << static_cast<int>(state_); |
| return -EINVAL; |
| } |
| |
| V4L2Format fmt(format); |
| fmt.SetType(buffer_type_); |
| if (V4L2_TYPE_IS_META(buffer_type_)) { |
| VLOGF(1) << "Device " << name_ << ": before VIDIOC_S_FMT fourcc: " |
| << FormatToString(fmt.PixelFormat()) |
| << ", size: " << fmt.SizeImage(0); |
| } else { |
| VLOGF(1) << "Device " << name_ << ": VIDIOC_S_FMT width: " << fmt.Width() |
| << ", height: " << fmt.Height() << ", bpl: " << fmt.BytesPerLine(0) |
| << ", fourcc: " << FormatToString(fmt.PixelFormat()) |
| << ", field: " << fmt.Field(); |
| } |
| |
| if (V4L2_TYPE_IS_META(buffer_type_)) { |
| fmt.SetSizeImage(0, 0); |
| } |
| |
| int ret = ::ioctl(fd_, VIDIOC_S_FMT, fmt.Get()); |
| if (ret < 0) { |
| PLOGF(ERROR) << "VIDIOC_S_FMT returned: " << ret; |
| return ret; |
| } |
| |
| if (V4L2_TYPE_IS_META(buffer_type_)) { |
| VLOGF(2) << "Device " << name_ << ": after VIDIOC_S_FMT fourcc: " |
| << FormatToString(fmt.PixelFormat()) |
| << ", size: " << fmt.SizeImage(0); |
| } else { |
| VLOGF(2) << "Device " << name_ |
| << ": after VIDIOC_S_FMT width: " << fmt.Width() |
| << ", height: " << fmt.Height() << ", bpl: " << fmt.BytesPerLine(0) |
| << ", fourcc: " << FormatToString(fmt.PixelFormat()) |
| << ", field: " << fmt.Field(); |
| } |
| |
| // Update current configuration with the new one |
| format_ = fmt; |
| |
| state_ = VideoNodeState::CONFIGURED; |
| return 0; |
| } |
| |
| int V4L2VideoNode::SetSelection(const struct v4l2_selection& selection) { |
| VLOGF_ENTER(); |
| base::AutoLock l(state_lock_); |
| if ((state_ != VideoNodeState::OPEN) && |
| (state_ != VideoNodeState::CONFIGURED)) { |
| LOGF(ERROR) << "Invalid device state " << static_cast<int>(state_); |
| return -EINVAL; |
| } |
| |
| struct v4l2_selection* sel = const_cast<struct v4l2_selection*>(&selection); |
| sel->type = buffer_type_; |
| VLOGF(1) << "Device " << name_ |
| << ": VIDIOC_S_SELECTION type: " << selection.type << ", target: 0x" |
| << std::hex << selection.target << ", flags: " << selection.flags |
| << ", rect left: " << std::dec << selection.r.left |
| << ", rect top: " << selection.r.top |
| << ", width: " << selection.r.width |
| << ", height: " << selection.r.height; |
| |
| int ret = ::ioctl(fd_, VIDIOC_S_SELECTION, sel); |
| if (ret < 0) { |
| PLOGF(ERROR) << "VIDIOC_S_SELECTION returned: " << ret; |
| } |
| return ret; |
| } |
| |
| int V4L2VideoNode::MapMemory(unsigned int index, |
| int prot, |
| int flags, |
| std::vector<void*>* mapped) { |
| VLOGF_ENTER(); |
| base::AutoLock l(state_lock_); |
| if ((state_ != VideoNodeState::OPEN) && |
| (state_ != VideoNodeState::CONFIGURED) && |
| (state_ != VideoNodeState::PREPARED)) { |
| LOGF(ERROR) << "Invalid device state " << static_cast<int>(state_); |
| return -EINVAL; |
| } |
| if (memory_type_ != V4L2_MEMORY_MMAP) { |
| LOGF(ERROR) << "Invalid memory type " << memory_type_; |
| return -EINVAL; |
| } |
| if (!mapped) { |
| return -EINVAL; |
| } |
| |
| V4L2Buffer buffer; |
| int ret = QueryBuffer(index, memory_type_, &buffer); |
| if (ret < 0) { |
| LOGF(ERROR) << name_ << " error querying buffers status"; |
| state_ = VideoNodeState::ERROR; |
| return ret; |
| } |
| uint32_t num_planes = |
| V4L2_TYPE_IS_MULTIPLANAR(buffer.Type()) ? buffer.Get()->length : 1; |
| for (uint32_t i = 0; i < num_planes; i++) { |
| void* res = |
| ::mmap(nullptr, buffer.Length(i), prot, flags, fd_, buffer.Offset(i)); |
| if (res == MAP_FAILED) { |
| PLOGF(ERROR) << "mmap failed"; |
| return -EINVAL; |
| } |
| mapped->push_back(res); |
| } |
| return 0; |
| } |
| |
| int V4L2VideoNode::GrabFrame(V4L2Buffer* buf) { |
| VLOGF_ENTER(); |
| base::AutoLock l(state_lock_); |
| if (state_ != VideoNodeState::STARTED) { |
| LOGF(ERROR) << name_ << " invalid device state " |
| << static_cast<int>(state_); |
| return -EINVAL; |
| } |
| if (!buf) { |
| LOGF(ERROR) << name_ << " invalid parameter buf is nullptr"; |
| return -EINVAL; |
| } |
| |
| int ret = Dqbuf(buf); |
| if (ret < 0) |
| return ret; |
| |
| PrintBufferInfo(__FUNCTION__, *buf); |
| return buf->Index(); |
| } |
| |
| int V4L2VideoNode::PutFrame(V4L2Buffer* buf) { |
| VLOGF_ENTER(); |
| |
| int ret = Qbuf(buf); |
| PrintBufferInfo(__FUNCTION__, *buf); |
| |
| return ret; |
| } |
| |
| int V4L2VideoNode::ExportFrame(unsigned int index, std::vector<int>* fds) { |
| if (memory_type_ != V4L2_MEMORY_MMAP) { |
| LOGF(ERROR) << name_ << " cannot export non-mmap buffers"; |
| return -EINVAL; |
| } |
| if (!fds) { |
| return -EINVAL; |
| } |
| |
| V4L2Buffer buffer; |
| int ret = QueryBuffer(index, memory_type_, &buffer); |
| if (ret < 0) { |
| LOGF(ERROR) << name_ << " error querying buffers status"; |
| state_ = VideoNodeState::ERROR; |
| return ret; |
| } |
| uint32_t num_planes = |
| V4L2_TYPE_IS_MULTIPLANAR(buffer.Type()) ? buffer.Get()->length : 1; |
| struct v4l2_exportbuffer ebuf = {}; |
| ebuf.type = buffer_type_; |
| ebuf.index = index; |
| ebuf.flags = O_RDWR; |
| for (uint32_t i = 0; i < num_planes; i++) { |
| ret = ::ioctl(fd_, VIDIOC_EXPBUF, &ebuf); |
| if (ret < 0) { |
| PLOGF(ERROR) << name_ << " VIDIOC_EXPBUF failed ret " << ret; |
| return ret; |
| } else { |
| fds->push_back(ebuf.fd); |
| } |
| VLOGF(2) << name_ << " idx " << index << " plane " << i << " fd " |
| << ebuf.fd; |
| } |
| return 0; |
| } |
| |
| int V4L2VideoNode::SetupBuffers(size_t num_buffers, |
| bool is_cached, |
| enum v4l2_memory memory_type, |
| std::vector<V4L2Buffer>* buffers) { |
| VLOGF_ENTER(); |
| if (num_buffers == 0 || !buffers || !buffers->empty()) { |
| return -EINVAL; |
| } |
| |
| base::AutoLock l(state_lock_); |
| if ((state_ != VideoNodeState::CONFIGURED)) { |
| LOGF(ERROR) << name_ |
| << " invalid operation, device not configured (state = " |
| << static_cast<int>(state_) << ")"; |
| return -EINVAL; |
| } |
| |
| int ret = RequestBuffers(num_buffers, memory_type); |
| if (ret <= 0) { |
| LOGF(ERROR) << name_ << " could not complete buffer request"; |
| return -EINVAL; |
| } |
| |
| for (size_t i = 0; i < num_buffers; i++) { |
| V4L2Buffer buffer; |
| int ret = QueryBuffer(i, memory_type, &buffer); |
| if (ret < 0) { |
| LOGF(ERROR) << name_ << " error querying buffers status"; |
| state_ = VideoNodeState::ERROR; |
| return ret; |
| } |
| buffers->push_back(std::move(buffer)); |
| } |
| |
| is_buffer_cached_ = is_cached; |
| memory_type_ = memory_type; |
| state_ = VideoNodeState::PREPARED; |
| return 0; |
| } |
| |
| int V4L2VideoNode::QueryCap(struct v4l2_capability* cap) { |
| VLOGF_ENTER(); |
| |
| int ret = ::ioctl(fd_, VIDIOC_QUERYCAP, cap); |
| |
| if (ret < 0) { |
| PLOGF(ERROR) << name_ << " VIDIOC_QUERYCAP returned: " << ret; |
| return ret; |
| } |
| |
| VLOGF(1) << "driver: " << cap->driver; |
| VLOGF(1) << "card: " << cap->card; |
| VLOGF(1) << "bus_info: " << cap->bus_info; |
| VLOGF(1) << "version: " << std::hex << cap->version; |
| VLOGF(1) << "capabilities: " << std::hex << cap->capabilities; |
| VLOGF(1) << "device caps: " << cap->device_caps; |
| VLOGF(1) << "buffer type " << buffer_type_; |
| |
| return 0; |
| } |
| |
| int V4L2VideoNode::RequestBuffers(size_t num_buffers, |
| enum v4l2_memory memory_type) { |
| VLOGF_ENTER(); |
| if (state_ == VideoNodeState::CLOSED) |
| return 0; |
| |
| struct v4l2_requestbuffers req_buf = {}; |
| req_buf.memory = memory_type; |
| req_buf.count = num_buffers; |
| req_buf.type = buffer_type_; |
| |
| VLOGF(1) << "Device " << name_ << ": VIDIOC_REQBUFS, count=" << req_buf.count |
| << ", memory=" << req_buf.memory << ", type=" << req_buf.type; |
| int ret = ::ioctl(fd_, VIDIOC_REQBUFS, &req_buf); |
| |
| if (ret < 0) { |
| PLOGF(ERROR) << name_ << " VIDIOC_REQBUFS(" << num_buffers |
| << ") returned: " << ret; |
| return ret; |
| } |
| |
| if (req_buf.count < num_buffers) |
| LOGF(WARNING) << name_ << " got less buffers than requested! " |
| << req_buf.count << " < " << num_buffers; |
| |
| memory_type_ = memory_type; |
| state_ = VideoNodeState::PREPARED; |
| return req_buf.count; |
| } |
| |
| void V4L2VideoNode::PrintBufferInfo(const std::string func, |
| const V4L2Buffer& buf) { |
| switch (memory_type_) { |
| case V4L2_MEMORY_USERPTR: |
| VLOGF(2) << func << " idx:" << buf.Index() << " addr:" << buf.Userptr(0); |
| break; |
| case V4L2_MEMORY_MMAP: |
| VLOGF(2) << func << " idx:" << buf.Index() << " offset:0x" << std::hex |
| << buf.Offset(0); |
| break; |
| case V4L2_MEMORY_DMABUF: |
| VLOGF(2) << func << " idx:" << buf.Index() << " fd:" << buf.Fd(0); |
| break; |
| default: |
| VLOGF(2) << func << " unknown memory type " << memory_type_; |
| break; |
| } |
| } |
| |
| int V4L2VideoNode::Qbuf(V4L2Buffer* buf) { |
| VLOGF_ENTER(); |
| |
| int ret = ::ioctl(fd_, VIDIOC_QBUF, buf->Get()); |
| if (ret < 0) { |
| PLOGF(ERROR) << name_ << " VIDIOC_QBUF failed"; |
| } |
| return ret; |
| } |
| |
| int V4L2VideoNode::Dqbuf(V4L2Buffer* buf) { |
| VLOGF_ENTER(); |
| buf->SetMemory(memory_type_); |
| buf->SetType(buffer_type_); |
| |
| int ret = ::ioctl(fd_, VIDIOC_DQBUF, buf->Get()); |
| if (ret < 0) { |
| PLOGF(ERROR) << name_ << " VIDIOC_DQBUF failed"; |
| } |
| return ret; |
| } |
| |
| int V4L2VideoNode::QueryBuffer(int index, |
| enum v4l2_memory memory_type, |
| V4L2Buffer* buf) { |
| VLOGF_ENTER(); |
| buf->SetFlags(0x0); |
| buf->SetMemory(memory_type); |
| buf->SetType(buffer_type_); |
| buf->SetIndex(index); |
| int ret = ::ioctl(fd_, VIDIOC_QUERYBUF, buf->Get()); |
| |
| if (ret < 0) { |
| PLOGF(ERROR) << name_ << " VIDIOC_QUERYBUF failed"; |
| return ret; |
| } |
| |
| VLOGF(1) << "Device " << name_ << ":"; |
| VLOGF(1) << " index " << buf->Index(); |
| VLOGF(1) << " type " << buf->Type(); |
| VLOGF(1) << " bytesused " << buf->BytesUsed(0); |
| VLOGF(1) << " flags 0x" << std::hex << buf->Flags(); |
| if (memory_type == V4L2_MEMORY_MMAP) { |
| VLOGF(1) << " memory MMAP: offset 0x" << std::hex << buf->Offset(0); |
| } else if (memory_type == V4L2_MEMORY_USERPTR) { |
| VLOGF(1) << " memory USRPTR: " << buf->Userptr(0); |
| } |
| VLOGF(1) << " length " << buf->Length(0); |
| return 0; |
| } |
| |
| int V4L2VideoNode::GetFormat(V4L2Format* format) { |
| VLOGF_ENTER(); |
| if (!format) { |
| return -EINVAL; |
| } |
| |
| base::AutoLock l(state_lock_); |
| if ((state_ != VideoNodeState::OPEN) && |
| (state_ != VideoNodeState::CONFIGURED)) { |
| LOGF(ERROR) << name_ << " invalid device state " |
| << static_cast<int>(state_); |
| return -EINVAL; |
| } |
| |
| v4l2_format fmt; |
| fmt.type = buffer_type_; |
| int ret = ::ioctl(fd_, VIDIOC_G_FMT, &fmt); |
| |
| if (ret < 0) { |
| PLOGF(ERROR) << name_ << " VIDIOC_G_FMT failed"; |
| return -EINVAL; |
| } |
| |
| *format = V4L2Format(fmt); |
| if (V4L2_TYPE_IS_META(buffer_type_)) { |
| VLOGF(1) << "Device " << name_ << ": VIDIOC_G_FMT fourcc: " |
| << FormatToString(format->PixelFormat()) |
| << ", size: " << format->SizeImage(0); |
| } else { |
| VLOGF(1) << "Device " << name_ |
| << ": VIDIOC_G_FMT width: " << format->Width() |
| << ", height: " << format->Height() |
| << ", bpl: " << format->BytesPerLine(0) |
| << ", fourcc: " << FormatToString(format->PixelFormat()) |
| << ", field: " << format->Field(); |
| } |
| |
| return 0; |
| } |
| |
| } // namespace cros |