| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/40285824): Remove this and spanify to fix the errors. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include "media/gpu/v4l2/v4l2_queue.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <libdrm/drm_fourcc.h> |
| #include <linux/media.h> |
| #include <poll.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| |
| #include "base/containers/contains.h" |
| #include "base/not_fatal_until.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/types/pass_key.h" |
| #include "media/gpu/chromeos/native_pixmap_frame_resource.h" |
| #include "media/gpu/chromeos/platform_video_frame_utils.h" |
| #include "media/gpu/macros.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // TODO(jkardatzke): Remove this when it is in linux/videodev2.h. |
| #define V4L2_MEMORY_FLAG_SECURE 0x2 |
| |
| // Maximum number of requests that can be created. |
| constexpr size_t kMaxNumRequests = 32; |
| |
| gfx::Rect V4L2RectToGfxRect(const v4l2_rect& rect) { |
| return gfx::Rect(rect.left, rect.top, rect.width, rect.height); |
| } |
| |
| struct v4l2_format BuildV4L2Format(const enum v4l2_buf_type type, |
| uint32_t fourcc, |
| const gfx::Size& size, |
| size_t buffer_size) { |
| struct v4l2_format format; |
| memset(&format, 0, sizeof(format)); |
| format.type = type; |
| format.fmt.pix_mp.pixelformat = fourcc; |
| format.fmt.pix_mp.width = size.width(); |
| format.fmt.pix_mp.height = size.height(); |
| format.fmt.pix_mp.num_planes = GetNumPlanesOfV4L2PixFmt(fourcc); |
| format.fmt.pix_mp.plane_fmt[0].sizeimage = buffer_size; |
| |
| return format; |
| } |
| |
| const char* V4L2BufferTypeToString(const enum v4l2_buf_type buf_type) { |
| switch (buf_type) { |
| case V4L2_BUF_TYPE_VIDEO_OUTPUT: |
| return "OUTPUT"; |
| case V4L2_BUF_TYPE_VIDEO_CAPTURE: |
| return "CAPTURE"; |
| case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: |
| return "OUTPUT_MPLANE"; |
| case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: |
| return "CAPTURE_MPLANE"; |
| default: |
| return "UNKNOWN"; |
| } |
| } |
| |
| int64_t V4L2BufferTimestampInMilliseconds( |
| const struct v4l2_buffer* v4l2_buffer) { |
| struct timespec ts; |
| TIMEVAL_TO_TIMESPEC(&v4l2_buffer->timestamp, &ts); |
| |
| return base::TimeDelta::FromTimeSpec(ts).InMilliseconds(); |
| } |
| |
| // For decoding and encoding data to be processed is enqueued in the |
| // V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE queue. Once that data has been either |
| // decompressed or compressed, the finished buffer is dequeued from the |
| // V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE queue. This occurs asynchronously so |
| // there is no way to measure how long the hardware took to process the data. |
| // We can use the length of time that a buffer is enqueued as a proxy for |
| // how busy the hardware is. |
| void V4L2ProcessingTrace(const struct v4l2_buffer* v4l2_buffer, bool start) { |
| constexpr char kTracingCategory[] = "media,gpu"; |
| constexpr char kQueueBuffer[] = "V4L2 Queue Buffer"; |
| constexpr char kDequeueBuffer[] = "V4L2 Dequeue Buffer"; |
| constexpr char kVideoDecoding[] = "V4L2 Video Decoding"; |
| |
| bool tracing_enabled = false; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTracingCategory, &tracing_enabled); |
| if (!tracing_enabled) { |
| return; |
| } |
| |
| const char* name = start ? kQueueBuffer : kDequeueBuffer; |
| TRACE_EVENT_INSTANT1(kTracingCategory, name, TRACE_EVENT_SCOPE_THREAD, "type", |
| v4l2_buffer->type); |
| |
| // TODO(mcasas): Consider using TimeValToTimeDelta(). |
| const int64_t timestamp = V4L2BufferTimestampInMilliseconds(v4l2_buffer); |
| if (timestamp <= 0) { |
| return; |
| } |
| |
| if (start && v4l2_buffer->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(kTracingCategory, kVideoDecoding, |
| TRACE_ID_LOCAL(timestamp), "timestamp", |
| timestamp); |
| } else if (!start && |
| v4l2_buffer->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { |
| TRACE_EVENT_NESTABLE_ASYNC_END1(kTracingCategory, kVideoDecoding, |
| TRACE_ID_LOCAL(timestamp), "timestamp", |
| timestamp); |
| } |
| } |
| |
| // Returns a vector of dmabuf file descriptors, exported for V4L2 buffer with |
| // |index|, assuming the buffer contains |num_planes| V4L2 planes and is of |
| // |buf_type|. Returns an empty vector on failure. The caller is responsible for |
| // closing the file descriptors after use. |
| std::vector<base::ScopedFD> GetDmabufsForV4L2Buffer( |
| const IoctlAsCallback& ioctl_cb, |
| int index, |
| size_t num_planes, |
| enum v4l2_buf_type buf_type) { |
| DVLOGF(3); |
| DCHECK(V4L2_TYPE_IS_MULTIPLANAR(buf_type)); |
| |
| std::vector<base::ScopedFD> dmabuf_fds; |
| for (size_t i = 0; i < num_planes; ++i) { |
| struct v4l2_exportbuffer expbuf; |
| memset(&expbuf, 0, sizeof(expbuf)); |
| expbuf.type = buf_type; |
| expbuf.index = index; |
| expbuf.plane = i; |
| expbuf.flags = O_CLOEXEC; |
| if (ioctl_cb.Run(VIDIOC_EXPBUF, &expbuf) != 0) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocExpbuf); |
| dmabuf_fds.clear(); |
| break; |
| } |
| |
| dmabuf_fds.push_back(base::ScopedFD(expbuf.fd)); |
| } |
| |
| return dmabuf_fds; |
| } |
| |
| } // namespace |
| |
| V4L2ExtCtrl::V4L2ExtCtrl(uint32_t id) { |
| memset(&ctrl, 0, sizeof(ctrl)); |
| ctrl.id = id; |
| } |
| |
| V4L2ExtCtrl::V4L2ExtCtrl(uint32_t id, int32_t val) : V4L2ExtCtrl(id) { |
| ctrl.value = val; |
| } |
| |
| // Class used to store the state of a buffer that should persist between |
| // reference creations. This includes: |
| // * Result of initial VIDIOC_QUERYBUF ioctl, |
| // * Plane mappings. |
| // |
| // Also provides helper functions. |
| class V4L2Buffer { |
| public: |
| static std::unique_ptr<V4L2Buffer> Create( |
| const IoctlAsCallback& ioctl_cb, |
| const MmapAsCallback& mmap_cb, |
| enum v4l2_buf_type type, |
| enum v4l2_memory memory, |
| const struct v4l2_format& format, |
| size_t buffer_id); |
| |
| V4L2Buffer(const V4L2Buffer&) = delete; |
| V4L2Buffer& operator=(const V4L2Buffer&) = delete; |
| |
| ~V4L2Buffer(); |
| |
| void* GetPlaneMapping(const size_t plane); |
| size_t GetMemoryUsage() const; |
| const struct v4l2_buffer& v4l2_buffer() const { return v4l2_buffer_; } |
| const scoped_refptr<FrameResource>& GetFrameResource(); |
| |
| private: |
| V4L2Buffer(const IoctlAsCallback& ioctl_cb, |
| const MmapAsCallback& mmap_cb, |
| enum v4l2_buf_type type, |
| enum v4l2_memory memory, |
| const struct v4l2_format& format, |
| size_t buffer_id); |
| bool Query(); |
| scoped_refptr<FrameResource> CreateFrame(); |
| |
| const IoctlAsCallback ioctl_cb_; |
| const MmapAsCallback mmap_cb_; |
| std::vector<void*> plane_mappings_; |
| |
| // V4L2 data as queried by QUERYBUF. |
| struct v4l2_buffer v4l2_buffer_; |
| // WARNING: do not change this to a vector or something smaller than |
| // VIDEO_MAX_PLANES (the maximum number of planes V4L2 supports). The |
| // element overhead is small and may avoid memory corruption bugs. |
| struct v4l2_plane v4l2_planes_[VIDEO_MAX_PLANES]; |
| |
| struct v4l2_format format_; |
| scoped_refptr<FrameResource> frame_; |
| base::WeakPtrFactory<V4L2Buffer> weak_factory_{this}; |
| }; |
| |
| std::unique_ptr<V4L2Buffer> V4L2Buffer::Create( |
| const IoctlAsCallback& ioctl_cb, |
| const MmapAsCallback& mmap_cb, |
| enum v4l2_buf_type type, |
| enum v4l2_memory memory, |
| const struct v4l2_format& format, |
| size_t buffer_id) { |
| // Not using std::make_unique because constructor is private. |
| std::unique_ptr<V4L2Buffer> buffer(new V4L2Buffer(std::move(ioctl_cb), |
| std::move(mmap_cb), type, |
| memory, format, buffer_id)); |
| if (!buffer->Query()) { |
| return nullptr; |
| } |
| |
| return buffer; |
| } |
| |
| V4L2Buffer::V4L2Buffer(const IoctlAsCallback& ioctl_cb, |
| const MmapAsCallback& mmap_cb, |
| enum v4l2_buf_type type, |
| enum v4l2_memory memory, |
| const struct v4l2_format& format, |
| size_t buffer_id) |
| : ioctl_cb_(ioctl_cb), mmap_cb_(mmap_cb), format_(format) { |
| DCHECK(V4L2_TYPE_IS_MULTIPLANAR(type)); |
| DCHECK_LE(format.fmt.pix_mp.num_planes, std::size(v4l2_planes_)); |
| |
| memset(&v4l2_buffer_, 0, sizeof(v4l2_buffer_)); |
| memset(v4l2_planes_, 0, sizeof(v4l2_planes_)); |
| v4l2_buffer_.m.planes = v4l2_planes_; |
| // Just in case we got more planes than we want. |
| v4l2_buffer_.length = |
| std::min(static_cast<size_t>(format.fmt.pix_mp.num_planes), |
| std::size(v4l2_planes_)); |
| v4l2_buffer_.index = buffer_id; |
| v4l2_buffer_.type = type; |
| v4l2_buffer_.memory = memory; |
| plane_mappings_.resize(v4l2_buffer_.length); |
| } |
| |
| V4L2Buffer::~V4L2Buffer() { |
| if (v4l2_buffer_.memory == V4L2_MEMORY_MMAP) { |
| for (size_t i = 0; i < plane_mappings_.size(); i++) { |
| if (plane_mappings_[i] != nullptr) { |
| munmap(plane_mappings_[i], v4l2_buffer_.m.planes[i].length); |
| } |
| } |
| } |
| } |
| |
| bool V4L2Buffer::Query() { |
| int ret = ioctl_cb_.Run(VIDIOC_QUERYBUF, &v4l2_buffer_); |
| if (ret) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocQuerybuf); |
| VPLOGF(1) << "VIDIOC_QUERYBUF failed: "; |
| return false; |
| } |
| |
| DCHECK(plane_mappings_.size() == v4l2_buffer_.length); |
| |
| return true; |
| } |
| |
| void* V4L2Buffer::GetPlaneMapping(const size_t plane) { |
| if (plane >= plane_mappings_.size()) { |
| VLOGF(1) << "Invalid plane " << plane << " requested."; |
| return nullptr; |
| } |
| |
| void* p = plane_mappings_[plane]; |
| if (p) { |
| return p; |
| } |
| |
| // Do this check here to avoid repeating it after a buffer has been |
| // successfully mapped (we know we are of MMAP type by then). |
| if (v4l2_buffer_.memory != V4L2_MEMORY_MMAP) { |
| VLOGF(1) << "Cannot create mapping on non-MMAP buffer"; |
| return nullptr; |
| } |
| |
| p = mmap_cb_.Run(nullptr, v4l2_buffer_.m.planes[plane].length, |
| PROT_READ | PROT_WRITE, MAP_SHARED, |
| v4l2_buffer_.m.planes[plane].m.mem_offset); |
| if (p == MAP_FAILED) { |
| VPLOGF(1) << "mmap() failed: "; |
| return nullptr; |
| } |
| |
| plane_mappings_[plane] = p; |
| return p; |
| } |
| |
| size_t V4L2Buffer::GetMemoryUsage() const { |
| size_t usage = 0; |
| for (size_t i = 0; i < v4l2_buffer_.length; i++) { |
| usage += v4l2_buffer_.m.planes[i].length; |
| } |
| return usage; |
| } |
| |
| scoped_refptr<FrameResource> V4L2Buffer::CreateFrame() { |
| auto layout = V4L2FormatToVideoFrameLayout(format_); |
| if (!layout) { |
| VLOGF(1) << "Cannot create frame layout for V4L2 buffers"; |
| return nullptr; |
| } |
| |
| std::vector<base::ScopedFD> dmabuf_fds = GetDmabufsForV4L2Buffer( |
| ioctl_cb_, v4l2_buffer_.index, v4l2_buffer_.length, |
| static_cast<enum v4l2_buf_type>(v4l2_buffer_.type)); |
| if (dmabuf_fds.empty()) { |
| VLOGF(1) << "Failed to get DMABUFs of V4L2 buffer"; |
| return nullptr; |
| } |
| |
| // DMA buffer fds should not be invalid |
| for (const auto& dmabuf_fd : dmabuf_fds) { |
| if (!dmabuf_fd.is_valid()) { |
| DLOG(ERROR) << "Fail to get DMABUFs of V4L2 buffer - invalid fd"; |
| return nullptr; |
| } |
| } |
| |
| // Duplicate the fd of the last v4l2 plane until the number of fds are the |
| // same as the number of color planes. |
| while (dmabuf_fds.size() < layout->planes().size()) { |
| int duped_fd = HANDLE_EINTR(dup(dmabuf_fds.back().get())); |
| if (duped_fd == -1) { |
| DLOG(ERROR) << "Failed duplicating dmabuf fd"; |
| return nullptr; |
| } |
| |
| dmabuf_fds.emplace_back(duped_fd); |
| } |
| |
| gfx::Size size(format_.fmt.pix_mp.width, format_.fmt.pix_mp.height); |
| |
| return NativePixmapFrameResource::Create( |
| *layout, gfx::Rect(size), size, std::move(dmabuf_fds), base::TimeDelta()); |
| } |
| |
| const scoped_refptr<FrameResource>& V4L2Buffer::GetFrameResource() { |
| // We can create the FrameResource only when using MMAP buffers. |
| if (v4l2_buffer_.memory != V4L2_MEMORY_MMAP) { |
| VLOGF(1) << "Cannot create video frame from non-MMAP buffer"; |
| // Allow NOTREACHED() on invalid argument because this is an internal |
| // method. |
| NOTREACHED(); |
| } |
| |
| // Create the video frame instance if requiring it for the first time. |
| if (!frame_) { |
| frame_ = CreateFrame(); |
| } |
| |
| return frame_; |
| } |
| |
| // A thread-safe pool of buffer indexes, allowing buffers to be obtained and |
| // returned from different threads. All the methods of this class are |
| // thread-safe. Users should keep a scoped_refptr to instances of this class |
| // in order to ensure the list remains alive as long as they need it. |
| class V4L2BuffersList : public base::RefCountedThreadSafe<V4L2BuffersList> { |
| public: |
| V4L2BuffersList() = default; |
| |
| V4L2BuffersList(const V4L2BuffersList&) = delete; |
| V4L2BuffersList& operator=(const V4L2BuffersList&) = delete; |
| |
| // Return a buffer to this list. Also can be called to set the initial pool |
| // of buffers. |
| // Note that it is illegal to return the same buffer twice. |
| void ReturnBuffer(size_t buffer_id); |
| // Get any of the buffers in the list. There is no order guarantee whatsoever. |
| std::optional<size_t> GetFreeBuffer(); |
| // Get the buffer with specified index. |
| std::optional<size_t> GetFreeBuffer(size_t requested_buffer_id); |
| // Number of buffers currently in this list. |
| size_t size() const; |
| |
| private: |
| friend class base::RefCountedThreadSafe<V4L2BuffersList>; |
| ~V4L2BuffersList() = default; |
| |
| mutable base::Lock lock_; |
| std::set<size_t> free_buffers_ GUARDED_BY(lock_); |
| }; |
| |
| void V4L2BuffersList::ReturnBuffer(size_t buffer_id) { |
| base::AutoLock auto_lock(lock_); |
| |
| auto inserted = free_buffers_.emplace(buffer_id); |
| DCHECK(inserted.second); |
| } |
| |
| std::optional<size_t> V4L2BuffersList::GetFreeBuffer() { |
| base::AutoLock auto_lock(lock_); |
| |
| auto iter = free_buffers_.begin(); |
| if (iter == free_buffers_.end()) { |
| DVLOGF(4) << "No free buffer available!"; |
| return std::nullopt; |
| } |
| |
| size_t buffer_id = *iter; |
| free_buffers_.erase(iter); |
| |
| return buffer_id; |
| } |
| |
| std::optional<size_t> V4L2BuffersList::GetFreeBuffer( |
| size_t requested_buffer_id) { |
| base::AutoLock auto_lock(lock_); |
| |
| return (free_buffers_.erase(requested_buffer_id) > 0) |
| ? std::make_optional(requested_buffer_id) |
| : std::nullopt; |
| } |
| |
| size_t V4L2BuffersList::size() const { |
| base::AutoLock auto_lock(lock_); |
| |
| return free_buffers_.size(); |
| } |
| |
| // Module-private class that let users query/write V4L2 buffer information. |
| // It also makes some private V4L2Queue methods available to this module only. |
| class V4L2BufferRefBase { |
| public: |
| V4L2BufferRefBase(const struct v4l2_buffer& v4l2_buffer, |
| base::WeakPtr<V4L2Queue> queue); |
| |
| V4L2BufferRefBase(const V4L2BufferRefBase&) = delete; |
| V4L2BufferRefBase& operator=(const V4L2BufferRefBase&) = delete; |
| |
| ~V4L2BufferRefBase(); |
| |
| bool QueueBuffer(scoped_refptr<FrameResource> frame); |
| void* GetPlaneMapping(const size_t plane); |
| |
| const scoped_refptr<FrameResource>& GetFrameResource(); |
| // Checks that the number of passed FDs is adequate for the current format |
| // and buffer configuration. Only useful for DMABUF buffers. |
| bool CheckNumFDsForFormat(const size_t num_fds) const; |
| |
| // Data from the buffer, that users can query and/or write. |
| struct v4l2_buffer v4l2_buffer_; |
| // WARNING: do not change this to a vector or something smaller than |
| // VIDEO_MAX_PLANES (the maximum number of planes V4L2 supports). The |
| // element overhead is small and may avoid memory corruption bugs. |
| struct v4l2_plane v4l2_planes_[VIDEO_MAX_PLANES]; |
| |
| private: |
| size_t BufferId() const { return v4l2_buffer_.index; } |
| |
| friend class V4L2WritableBufferRef; |
| // A weak pointer to the queue this buffer belongs to. Will remain valid as |
| // long as the underlying V4L2 buffer is valid too. |
| // This can only be accessed from the sequence protected by sequence_checker_. |
| // Thread-safe methods (like ~V4L2BufferRefBase) must *never* access this. |
| base::WeakPtr<V4L2Queue> queue_; |
| // Where to return this buffer if it goes out of scope without being queued. |
| scoped_refptr<V4L2BuffersList> return_to_; |
| bool queued = false; |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| }; |
| |
| V4L2BufferRefBase::V4L2BufferRefBase(const struct v4l2_buffer& v4l2_buffer, |
| base::WeakPtr<V4L2Queue> queue) |
| : queue_(std::move(queue)), return_to_(queue_->free_buffers_) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(V4L2_TYPE_IS_MULTIPLANAR(v4l2_buffer.type)); |
| DCHECK_LE(v4l2_buffer.length, std::size(v4l2_planes_)); |
| DCHECK(return_to_); |
| |
| memcpy(&v4l2_buffer_, &v4l2_buffer, sizeof(v4l2_buffer_)); |
| memcpy(v4l2_planes_, v4l2_buffer.m.planes, |
| sizeof(struct v4l2_plane) * v4l2_buffer.length); |
| v4l2_buffer_.m.planes = v4l2_planes_; |
| } |
| |
| V4L2BufferRefBase::~V4L2BufferRefBase() { |
| // We are the last reference and are only accessing the thread-safe |
| // return_to_, so we are safe to call from any sequence. |
| // If we have been queued, then the queue is our owner so we don't need to |
| // return to the free buffers list. |
| if (!queued) { |
| return_to_->ReturnBuffer(BufferId()); |
| } |
| } |
| |
| bool V4L2BufferRefBase::QueueBuffer(scoped_refptr<FrameResource> frame) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!queue_) { |
| return false; |
| } |
| |
| queued = queue_->QueueBuffer(&v4l2_buffer_, std::move(frame)); |
| |
| return queued; |
| } |
| |
| void* V4L2BufferRefBase::GetPlaneMapping(const size_t plane) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!queue_) { |
| return nullptr; |
| } |
| |
| return queue_->buffers_[BufferId()]->GetPlaneMapping(plane); |
| } |
| |
| const scoped_refptr<FrameResource>& V4L2BufferRefBase::GetFrameResource() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Used so we can return a const scoped_refptr& in all cases. |
| static const scoped_refptr<FrameResource> null_frame_resource; |
| |
| if (!queue_) { |
| return null_frame_resource; |
| } |
| |
| DCHECK_LE(BufferId(), queue_->buffers_.size()); |
| |
| return queue_->buffers_[BufferId()]->GetFrameResource(); |
| } |
| |
| bool V4L2BufferRefBase::CheckNumFDsForFormat(const size_t num_fds) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!queue_) { |
| return false; |
| } |
| |
| // We have not used SetFormat(), assume this is ok. |
| // Hopefully we standardize SetFormat() in the future. |
| if (!queue_->current_format_) { |
| return true; |
| } |
| |
| const size_t required_fds = queue_->current_format_->fmt.pix_mp.num_planes; |
| // Sanity check. |
| DCHECK_EQ(v4l2_buffer_.length, required_fds); |
| if (num_fds < required_fds) { |
| VLOGF(1) << "Insufficient number of FDs given for the current format. " |
| << num_fds << " provided, " << required_fds << " required."; |
| return false; |
| } |
| |
| const auto* planes = v4l2_buffer_.m.planes; |
| for (size_t i = v4l2_buffer_.length - 1; i >= num_fds; --i) { |
| // Assume that an fd is a duplicate of a previous plane's fd if offset != 0. |
| // Otherwise, if offset == 0, return error as it is likely pointing to |
| // a new plane. |
| if (planes[i].data_offset == 0) { |
| VLOGF(1) << "Additional dmabuf fds point to a new buffer."; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| V4L2WritableBufferRef::V4L2WritableBufferRef( |
| const struct v4l2_buffer& v4l2_buffer, |
| base::WeakPtr<V4L2Queue> queue) |
| : buffer_data_( |
| std::make_unique<V4L2BufferRefBase>(v4l2_buffer, std::move(queue))) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| V4L2WritableBufferRef::V4L2WritableBufferRef(V4L2WritableBufferRef&& other) |
| : buffer_data_(std::move(other.buffer_data_)) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(other.sequence_checker_); |
| } |
| |
| V4L2WritableBufferRef::~V4L2WritableBufferRef() { |
| // Only valid references should be sequence-checked |
| if (buffer_data_) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| } |
| |
| V4L2WritableBufferRef& V4L2WritableBufferRef::operator=( |
| V4L2WritableBufferRef&& other) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(other.sequence_checker_); |
| |
| if (this == &other) { |
| return *this; |
| } |
| |
| buffer_data_ = std::move(other.buffer_data_); |
| |
| return *this; |
| } |
| |
| scoped_refptr<FrameResource> V4L2WritableBufferRef::GetFrameResource() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->GetFrameResource(); |
| } |
| |
| enum v4l2_memory V4L2WritableBufferRef::Memory() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return static_cast<enum v4l2_memory>(buffer_data_->v4l2_buffer_.memory); |
| } |
| |
| bool V4L2WritableBufferRef::DoQueue(V4L2RequestRef* request_ref, |
| scoped_refptr<FrameResource> frame) && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| if (request_ref && buffer_data_->queue_->SupportsRequests() && |
| !request_ref->ApplyQueueBuffer(&(buffer_data_->v4l2_buffer_))) { |
| return false; |
| } |
| |
| bool queued = buffer_data_->QueueBuffer(std::move(frame)); |
| |
| // Clear our own reference. |
| buffer_data_.reset(); |
| |
| return queued; |
| } |
| |
| bool V4L2WritableBufferRef::QueueMMap(V4L2RequestRef* request_ref) && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| // Move ourselves so our data gets freed no matter when we return |
| V4L2WritableBufferRef self(std::move(*this)); |
| |
| if (self.Memory() != V4L2_MEMORY_MMAP) { |
| VLOGF(1) << "Called on invalid buffer type!"; |
| return false; |
| } |
| |
| return std::move(self).DoQueue(request_ref, nullptr); |
| } |
| |
| bool V4L2WritableBufferRef::QueueUserPtr(const std::vector<void*>& ptrs, |
| V4L2RequestRef* request_ref) && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| // Move ourselves so our data gets freed no matter when we return |
| V4L2WritableBufferRef self(std::move(*this)); |
| |
| if (self.Memory() != V4L2_MEMORY_USERPTR) { |
| VLOGF(1) << "Called on invalid buffer type!"; |
| return false; |
| } |
| |
| if (ptrs.size() != self.PlanesCount()) { |
| VLOGF(1) << "Provided " << ptrs.size() << " pointers while we require " |
| << self.buffer_data_->v4l2_buffer_.length << "."; |
| return false; |
| } |
| |
| for (size_t i = 0; i < ptrs.size(); i++) { |
| self.buffer_data_->v4l2_buffer_.m.planes[i].m.userptr = |
| reinterpret_cast<unsigned long>(ptrs[i]); |
| } |
| |
| return std::move(self).DoQueue(request_ref, nullptr); |
| } |
| |
| bool V4L2WritableBufferRef::QueueDMABuf(const std::vector<base::ScopedFD>& fds, |
| V4L2RequestRef* request_ref) && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| // Move ourselves so our data gets freed no matter when we return |
| V4L2WritableBufferRef self(std::move(*this)); |
| |
| if (self.Memory() != V4L2_MEMORY_DMABUF) { |
| VLOGF(1) << "Called on invalid buffer type!"; |
| return false; |
| } |
| |
| if (!self.buffer_data_->CheckNumFDsForFormat(fds.size())) { |
| return false; |
| } |
| |
| size_t num_planes = self.PlanesCount(); |
| for (size_t i = 0; i < num_planes; i++) { |
| self.buffer_data_->v4l2_buffer_.m.planes[i].m.fd = fds[i].get(); |
| } |
| |
| return std::move(self).DoQueue(request_ref, nullptr); |
| } |
| |
| bool V4L2WritableBufferRef::QueueDMABuf(scoped_refptr<FrameResource> frame, |
| V4L2RequestRef* request_ref) && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| // Move ourselves so our data gets freed no matter when we return |
| V4L2WritableBufferRef self(std::move(*this)); |
| |
| if (self.Memory() != V4L2_MEMORY_DMABUF) { |
| VLOGF(1) << "Called on invalid buffer type!"; |
| return false; |
| } |
| |
| // TODO(andrescj): consider replacing this by a DCHECK. |
| if (frame->storage_type() != VideoFrame::STORAGE_GPU_MEMORY_BUFFER && |
| frame->storage_type() != VideoFrame::STORAGE_DMABUFS) { |
| VLOGF(1) << "Only frames with GpuMemoryBuffer and dma-buf are supported"; |
| return false; |
| } |
| |
| // The FDs duped by CreateGpuMemoryBufferHandle() will be closed after the |
| // call to DoQueue() which uses the VIDIOC_QBUF ioctl and so ends up |
| // increasing the reference count of the dma-buf. Thus, closing the FDs is |
| // safe. |
| // TODO(andrescj): for dma-buf frames, duping the FDs is unnecessary. |
| // Consider handling that path separately. |
| gfx::GpuMemoryBufferHandle gmb_handle = frame->CreateGpuMemoryBufferHandle(); |
| if (gmb_handle.type != gfx::GpuMemoryBufferType::NATIVE_PIXMAP) { |
| VLOGF(1) << "Failed to create GpuMemoryBufferHandle for frame!"; |
| return false; |
| } |
| const std::vector<gfx::NativePixmapPlane>& planes = |
| gmb_handle.native_pixmap_handle.planes; |
| |
| if (!self.buffer_data_->CheckNumFDsForFormat(planes.size())) { |
| return false; |
| } |
| |
| size_t num_planes = self.PlanesCount(); |
| for (size_t i = 0; i < num_planes; i++) { |
| self.buffer_data_->v4l2_buffer_.m.planes[i].m.fd = planes[i].fd.get(); |
| } |
| |
| return std::move(self).DoQueue(request_ref, std::move(frame)); |
| } |
| |
| bool V4L2WritableBufferRef::QueueDMABuf( |
| const std::vector<gfx::NativePixmapPlane>& planes, |
| V4L2RequestRef* request_ref) && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| // Move ourselves so our data gets freed no matter when we return |
| V4L2WritableBufferRef self(std::move(*this)); |
| |
| if (self.Memory() != V4L2_MEMORY_DMABUF) { |
| VLOGF(1) << "Called on invalid buffer type!"; |
| return false; |
| } |
| |
| if (!self.buffer_data_->CheckNumFDsForFormat(planes.size())) { |
| return false; |
| } |
| |
| size_t num_planes = self.PlanesCount(); |
| for (size_t i = 0; i < num_planes; i++) { |
| self.buffer_data_->v4l2_buffer_.m.planes[i].m.fd = planes[i].fd.get(); |
| } |
| |
| return std::move(self).DoQueue(request_ref, nullptr); |
| } |
| |
| bool V4L2WritableBufferRef::QueueDMABuf(uint64_t secure_handle, |
| V4L2RequestRef* request_ref) && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| // Move ourselves so our data gets freed no matter when we return |
| V4L2WritableBufferRef self(std::move(*this)); |
| |
| if (self.Memory() != V4L2_MEMORY_DMABUF) { |
| VLOGF(1) << "Called on invalid buffer type!"; |
| return false; |
| } |
| |
| // Set the FD for the secure handle. |
| bool set_fd = self.buffer_data_->queue_->SetBufferFdForSecureHandle( |
| secure_handle, &self.buffer_data_->v4l2_buffer_); |
| if (!set_fd) { |
| return false; |
| } |
| |
| // The FD should already be set in the plane data, so submit it. |
| return std::move(self).DoQueue(request_ref, nullptr); |
| } |
| |
| size_t V4L2WritableBufferRef::PlanesCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->v4l2_buffer_.length; |
| } |
| |
| size_t V4L2WritableBufferRef::GetPlaneSize(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| if (plane >= PlanesCount()) { |
| VLOGF(1) << "Invalid plane " << plane << " requested."; |
| return 0; |
| } |
| |
| return buffer_data_->v4l2_buffer_.m.planes[plane].length; |
| } |
| |
| void V4L2WritableBufferRef::SetPlaneSize(const size_t plane, |
| const size_t size) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| enum v4l2_memory memory = Memory(); |
| if (memory == V4L2_MEMORY_MMAP) { |
| DCHECK_EQ(buffer_data_->v4l2_buffer_.m.planes[plane].length, size); |
| return; |
| } |
| DCHECK(memory == V4L2_MEMORY_USERPTR || memory == V4L2_MEMORY_DMABUF); |
| |
| if (plane >= PlanesCount()) { |
| VLOGF(1) << "Invalid plane " << plane << " requested."; |
| return; |
| } |
| |
| buffer_data_->v4l2_buffer_.m.planes[plane].length = size; |
| } |
| |
| void* V4L2WritableBufferRef::GetPlaneMapping(const size_t plane) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->GetPlaneMapping(plane); |
| } |
| |
| void V4L2WritableBufferRef::SetTimeStamp(const struct timeval& timestamp) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| buffer_data_->v4l2_buffer_.timestamp = timestamp; |
| } |
| |
| const struct timeval& V4L2WritableBufferRef::GetTimeStamp() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->v4l2_buffer_.timestamp; |
| } |
| |
| void V4L2WritableBufferRef::SetPlaneBytesUsed(const size_t plane, |
| const size_t bytes_used) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| if (plane >= PlanesCount()) { |
| VLOGF(1) << "Invalid plane " << plane << " requested."; |
| return; |
| } |
| |
| if (bytes_used > GetPlaneSize(plane)) { |
| VLOGF(1) << "Set bytes used " << bytes_used << " larger than plane size " |
| << GetPlaneSize(plane) << "."; |
| return; |
| } |
| |
| buffer_data_->v4l2_buffer_.m.planes[plane].bytesused = bytes_used; |
| } |
| |
| size_t V4L2WritableBufferRef::GetPlaneBytesUsed(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| if (plane >= PlanesCount()) { |
| VLOGF(1) << "Invalid plane " << plane << " requested."; |
| return 0; |
| } |
| |
| return buffer_data_->v4l2_buffer_.m.planes[plane].bytesused; |
| } |
| |
| void V4L2WritableBufferRef::SetPlaneDataOffset(const size_t plane, |
| const size_t data_offset) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| if (plane >= PlanesCount()) { |
| VLOGF(1) << "Invalid plane " << plane << " requested."; |
| return; |
| } |
| |
| buffer_data_->v4l2_buffer_.m.planes[plane].data_offset = data_offset; |
| } |
| |
| size_t V4L2WritableBufferRef::BufferId() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->v4l2_buffer_.index; |
| } |
| |
| V4L2ReadableBuffer::V4L2ReadableBuffer(base::PassKey<V4L2BufferRefFactory>, |
| const struct v4l2_buffer& v4l2_buffer, |
| base::WeakPtr<V4L2Queue> queue, |
| scoped_refptr<FrameResource> frame) |
| : buffer_data_( |
| std::make_unique<V4L2BufferRefBase>(v4l2_buffer, std::move(queue))), |
| frame_(std::move(frame)) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| scoped_refptr<FrameResource> V4L2ReadableBuffer::GetFrameResource() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| if (buffer_data_->v4l2_buffer_.memory == V4L2_MEMORY_DMABUF && frame_) { |
| return frame_; |
| } |
| return buffer_data_->GetFrameResource(); |
| } |
| |
| V4L2ReadableBuffer::~V4L2ReadableBuffer() { |
| // This method is thread-safe. Since we are the destructor, we are guaranteed |
| // to be called from the only remaining reference to us. Also, we are just |
| // calling the destructor of buffer_data_, which is also thread-safe. |
| DCHECK(buffer_data_); |
| } |
| |
| bool V4L2ReadableBuffer::IsLast() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->v4l2_buffer_.flags & V4L2_BUF_FLAG_LAST; |
| } |
| |
| bool V4L2ReadableBuffer::IsKeyframe() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->v4l2_buffer_.flags & V4L2_BUF_FLAG_KEYFRAME; |
| } |
| |
| bool V4L2ReadableBuffer::IsError() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| // "The driver may also set V4L2_BUF_FLAG_ERROR in the flags field. It |
| // indicates a non-critical (recoverable) streaming error. In such case the |
| // application may continue as normal, but should be aware that data in the |
| // dequeued buffer might be corrupted." IOW it is more a discard-this-buffer |
| // marker than a fatal error indication, so it's down to the caller to take |
| // action if needed/desired. |
| // https://www.kernel.org/doc/html/v5.15/userspace-api/media/v4l/vidioc-qbuf.html#description |
| return buffer_data_->v4l2_buffer_.flags & V4L2_BUF_FLAG_ERROR; |
| } |
| |
| struct timeval V4L2ReadableBuffer::GetTimeStamp() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->v4l2_buffer_.timestamp; |
| } |
| |
| size_t V4L2ReadableBuffer::PlanesCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->v4l2_buffer_.length; |
| } |
| |
| const void* V4L2ReadableBuffer::GetPlaneMapping(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->GetPlaneMapping(plane); |
| } |
| |
| size_t V4L2ReadableBuffer::GetPlaneBytesUsed(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| if (plane >= PlanesCount()) { |
| VLOGF(1) << "Invalid plane " << plane << " requested."; |
| return 0; |
| } |
| |
| return buffer_data_->v4l2_planes_[plane].bytesused; |
| } |
| |
| size_t V4L2ReadableBuffer::GetPlaneDataOffset(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| if (plane >= PlanesCount()) { |
| VLOGF(1) << "Invalid plane " << plane << " requested."; |
| return 0; |
| } |
| |
| return buffer_data_->v4l2_planes_[plane].data_offset; |
| } |
| |
| size_t V4L2ReadableBuffer::BufferId() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer_data_); |
| |
| return buffer_data_->v4l2_buffer_.index; |
| } |
| |
| struct SecureBufferData { |
| SecureBufferData(uint64_t in_secure_handle, base::ScopedFD in_fd) |
| : secure_handle(in_secure_handle), fd(std::move(in_fd)) {} |
| SecureBufferData(SecureBufferData&& other) = default; |
| ~SecureBufferData() {} |
| // true if the secure buffer stores decrypted data from an active |
| // DecoderBuffer. |
| bool owned_by_decoder_buffer = false; |
| // List of all the buffer indexes that have this FD/secure_handle currently |
| // attached to it. |
| std::vector<size_t> queued_buffer_indexes; |
| uint64_t secure_handle; |
| base::ScopedFD fd; |
| }; |
| |
| // Helper macros that print the queue type with logs. |
| #define VPQLOGF(level) \ |
| VPLOGF(level) << "(" << V4L2BufferTypeToString(type_) << ") " |
| #define VQLOGF(level) \ |
| VLOGF(level) << "(" << V4L2BufferTypeToString(type_) << ") " |
| #define DVQLOGF(level) \ |
| DVLOGF(level) << "(" << V4L2BufferTypeToString(type_) << ") " |
| |
| V4L2Queue::V4L2Queue(base::PassKey<PassKey>, |
| const IoctlAsCallback& ioctl_cb, |
| const base::RepeatingClosure& schedule_poll_cb, |
| const MmapAsCallback& mmap_cb, |
| const AllocateSecureBufferAsCallback& allocate_secure_cb, |
| enum v4l2_buf_type type, |
| base::OnceClosure destroy_cb) |
| : type_(type), |
| ioctl_cb_(ioctl_cb), |
| schedule_poll_cb_(schedule_poll_cb), |
| mmap_cb_(mmap_cb), |
| allocate_secure_cb_(allocate_secure_cb), |
| destroy_cb_(std::move(destroy_cb)), |
| weak_this_factory_(this) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| struct v4l2_requestbuffers reqbufs = { |
| .count = 0, .type = type_, .memory = V4L2_MEMORY_MMAP}; |
| supports_requests_ = (ioctl_cb_.Run(VIDIOC_REQBUFS, &reqbufs) == kIoctlOk) && |
| (reqbufs.capabilities & V4L2_BUF_CAP_SUPPORTS_REQUESTS); |
| |
| // Stateful backends for example do not support requests. |
| VPLOG_IF(4, supports_requests_) |
| << "This queue does " << (supports_requests_ ? "" : "not") |
| << " support requests."; |
| } |
| |
| V4L2Queue::~V4L2Queue() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (is_streaming_ && !Streamoff()) { |
| VQLOGF(1) << "Failed to stop queue"; |
| } |
| |
| DCHECK(queued_buffers_.empty()); |
| |
| if (!buffers_.empty() && !DeallocateBuffers()) { |
| VQLOGF(1) << "Failed to deallocate queue buffers"; |
| } |
| |
| std::move(destroy_cb_).Run(); |
| } |
| |
| std::optional<struct v4l2_format> V4L2Queue::SetFormat(uint32_t fourcc, |
| const gfx::Size& size, |
| size_t buffer_size) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| struct v4l2_format format = BuildV4L2Format(type_, fourcc, size, buffer_size); |
| if (ioctl_cb_.Run(VIDIOC_S_FMT, &format) != 0 || |
| format.fmt.pix_mp.pixelformat != fourcc) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocSFmt); |
| VPQLOGF(2) << "Failed to set format fourcc: " << FourccToString(fourcc); |
| return std::nullopt; |
| } |
| |
| current_format_ = format; |
| return current_format_; |
| } |
| |
| std::optional<struct v4l2_format> V4L2Queue::TryFormat(uint32_t fourcc, |
| const gfx::Size& size, |
| size_t buffer_size) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| struct v4l2_format format = BuildV4L2Format(type_, fourcc, size, buffer_size); |
| if (ioctl_cb_.Run(VIDIOC_TRY_FMT, &format) != 0 || |
| format.fmt.pix_mp.pixelformat != fourcc) { |
| VPQLOGF(2) << "Failed to try format fourcc: " << FourccToString(fourcc); |
| return std::nullopt; |
| } |
| |
| return format; |
| } |
| |
| std::pair<std::optional<struct v4l2_format>, int> V4L2Queue::GetFormat() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| struct v4l2_format format; |
| memset(&format, 0, sizeof(format)); |
| format.type = type_; |
| if (ioctl_cb_.Run(VIDIOC_G_FMT, &format) != 0) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocGFmt); |
| VPQLOGF(2) << "Failed to get format"; |
| return std::make_pair(std::nullopt, errno); |
| } |
| |
| return std::make_pair(format, 0); |
| } |
| |
| std::optional<gfx::Rect> V4L2Queue::GetVisibleRect() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| struct v4l2_selection selection = {.type = type_, |
| .target = V4L2_SEL_TGT_COMPOSE}; |
| if (ioctl_cb_.Run(VIDIOC_G_SELECTION, &selection) != 0) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocGSelection); |
| VQLOGF(1) << "Failed to get visible rect"; |
| return std::nullopt; |
| } |
| return V4L2RectToGfxRect(selection.r); |
| } |
| |
| size_t V4L2Queue::AllocateBuffers(size_t count, |
| enum v4l2_memory memory, |
| bool incoherent) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!free_buffers_); |
| DCHECK(queued_buffers_.empty()); |
| |
| incoherent_ = incoherent; |
| |
| if (IsStreaming()) { |
| VQLOGF(1) << "Cannot allocate buffers while streaming."; |
| return 0; |
| } |
| |
| if (buffers_.size() != 0) { |
| VQLOGF(1) |
| << "Cannot allocate new buffers while others are still allocated."; |
| return 0; |
| } |
| // Should have been cleared in DeallocateBuffers() if it was ever filled in. |
| DCHECK(free_buffers_indexes_.empty()); |
| |
| if (count == 0) { |
| VQLOGF(1) << "Attempting to allocate 0 buffers."; |
| return 0; |
| } |
| |
| // First query the number of planes in the buffers we are about to request. |
| std::optional<v4l2_format> format = GetFormat().first; |
| if (!format) { |
| VQLOGF(1) << "Cannot get format."; |
| return 0; |
| } |
| planes_count_ = format->fmt.pix_mp.num_planes; |
| DCHECK_LE(planes_count_, static_cast<size_t>(VIDEO_MAX_PLANES)); |
| |
| __u8 flags = incoherent ? V4L2_MEMORY_FLAG_NON_COHERENT : 0; |
| if (allocate_secure_cb_) { |
| flags |= V4L2_MEMORY_FLAG_SECURE; |
| } |
| struct v4l2_requestbuffers reqbufs = { |
| .count = base::checked_cast<decltype(v4l2_requestbuffers::count)>(count), |
| .type = type_, |
| .memory = memory, |
| .flags = flags}; |
| DVQLOGF(3) << "Requesting " << count << " buffers (" |
| << (incoherent ? "incoherent" : "coherent") << ")"; |
| |
| int ret = ioctl_cb_.Run(VIDIOC_REQBUFS, &reqbufs); |
| if (ret) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocReqbufs); |
| VPQLOGF(1) << "VIDIOC_REQBUFS failed"; |
| return 0; |
| } |
| DVQLOGF(3) << "Allocated " << reqbufs.count << " buffers."; |
| |
| memory_ = memory; |
| |
| free_buffers_ = new V4L2BuffersList(); |
| |
| // Now query all buffer information. |
| for (size_t i = 0; i < reqbufs.count; i++) { |
| auto buffer = |
| V4L2Buffer::Create(ioctl_cb_, mmap_cb_, type_, memory_, *format, i); |
| |
| if (!buffer) { |
| if (!DeallocateBuffers()) { |
| VQLOGF(1) << "Failed to deallocate queue buffers"; |
| } |
| |
| return 0; |
| } |
| |
| if (type_ == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && allocate_secure_cb_) { |
| CHECK_EQ(memory_, V4L2_MEMORY_DMABUF); |
| // Invoke the callback for secure buffer allocation. We only use dmabufs |
| // for the OUTPUT queue when doing secure playback. |
| allocate_secure_cb_.Run(buffer->v4l2_buffer().m.planes[0].length, |
| base::BindOnce(&V4L2Queue::SecureBufferAllocated, |
| weak_this_factory_.GetWeakPtr())); |
| } |
| |
| buffers_.emplace_back(std::move(buffer)); |
| free_buffers_->ReturnBuffer(i); |
| } |
| |
| DCHECK(free_buffers_); |
| DCHECK_EQ(free_buffers_->size(), buffers_.size()); |
| DCHECK(queued_buffers_.empty()); |
| |
| return buffers_.size(); |
| } |
| |
| bool V4L2Queue::DeallocateBuffers() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (IsStreaming()) { |
| VQLOGF(1) << "Cannot deallocate buffers while streaming."; |
| return false; |
| } |
| |
| if (buffers_.size() == 0) { |
| return true; |
| } |
| |
| weak_this_factory_.InvalidateWeakPtrs(); |
| buffers_.clear(); |
| free_buffers_indexes_.clear(); |
| free_buffers_ = nullptr; |
| secure_buffers_.clear(); |
| |
| // Free all buffers. |
| __u8 flags = incoherent_ ? V4L2_MEMORY_FLAG_NON_COHERENT : 0; |
| if (allocate_secure_cb_) { |
| flags |= V4L2_MEMORY_FLAG_SECURE; |
| } |
| struct v4l2_requestbuffers reqbufs = { |
| .count = 0, .type = type_, .memory = memory_, .flags = flags}; |
| |
| int ret = ioctl_cb_.Run(VIDIOC_REQBUFS, &reqbufs); |
| if (ret) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocReqbufs); |
| VPQLOGF(1) << "VIDIOC_REQBUFS failed"; |
| return false; |
| } |
| |
| DCHECK(!free_buffers_); |
| DCHECK(queued_buffers_.empty()); |
| |
| return true; |
| } |
| |
| size_t V4L2Queue::GetMemoryUsage() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| size_t usage = 0; |
| for (const auto& buf : buffers_) { |
| usage += buf->GetMemoryUsage(); |
| } |
| return usage; |
| } |
| |
| v4l2_memory V4L2Queue::GetMemoryType() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return memory_; |
| } |
| |
| // This class is used to expose buffer reference classes constructors to |
| // this module. This is to ensure that nobody else can create buffer references. |
| class V4L2BufferRefFactory { |
| public: |
| static V4L2WritableBufferRef CreateWritableRef( |
| const struct v4l2_buffer& v4l2_buffer, |
| base::WeakPtr<V4L2Queue> queue) { |
| return V4L2WritableBufferRef(v4l2_buffer, std::move(queue)); |
| } |
| |
| static V4L2ReadableBufferRef CreateReadableRef( |
| const struct v4l2_buffer& v4l2_buffer, |
| base::WeakPtr<V4L2Queue> queue, |
| scoped_refptr<FrameResource> frame) { |
| return base::MakeRefCounted<V4L2ReadableBuffer>( |
| base::PassKey<V4L2BufferRefFactory>(), v4l2_buffer, std::move(queue), |
| std::move(frame)); |
| } |
| }; |
| |
| CroStatus::Or<uint64_t> V4L2Queue::GetFreeSecureHandle() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Go through the list of secure buffers and find one that is not owned or |
| // queued. |
| for (auto& buf : secure_buffers_) { |
| if (!buf.owned_by_decoder_buffer && buf.queued_buffer_indexes.empty()) { |
| buf.owned_by_decoder_buffer = true; |
| return buf.secure_handle; |
| } |
| } |
| return CroStatus::Codes::kSecureBufferPoolEmpty; |
| } |
| |
| void V4L2Queue::ReleaseSecureHandle(uint64_t secure_handle) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Find the matching secure buffer and release the ownership on it, it might |
| // still be in use, but that would be tracked by the queue counter if so. |
| for (auto& buf : secure_buffers_) { |
| if (buf.secure_handle == secure_handle) { |
| buf.owned_by_decoder_buffer = false; |
| return; |
| } |
| } |
| } |
| |
| std::optional<V4L2WritableBufferRef> V4L2Queue::GetFreeBuffer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // No buffers allocated at the moment? |
| if (!free_buffers_) { |
| return std::nullopt; |
| } |
| |
| auto buffer_id = free_buffers_->GetFreeBuffer(); |
| if (!buffer_id.has_value()) { |
| return std::nullopt; |
| } |
| |
| return V4L2BufferRefFactory::CreateWritableRef( |
| buffers_[buffer_id.value()]->v4l2_buffer(), |
| weak_this_factory_.GetWeakPtr()); |
| } |
| |
| std::optional<V4L2WritableBufferRef> V4L2Queue::GetFreeBuffer( |
| size_t requested_buffer_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // No buffers allocated at the moment? |
| if (!free_buffers_) { |
| return std::nullopt; |
| } |
| |
| auto buffer_id = free_buffers_->GetFreeBuffer(requested_buffer_id); |
| if (!buffer_id.has_value()) { |
| return std::nullopt; |
| } |
| |
| return V4L2BufferRefFactory::CreateWritableRef( |
| buffers_[buffer_id.value()]->v4l2_buffer(), |
| weak_this_factory_.GetWeakPtr()); |
| } |
| |
| std::optional<V4L2WritableBufferRef> V4L2Queue::GetFreeBufferForFrame( |
| const base::UnguessableToken& id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // No buffers allocated at the moment? |
| if (!free_buffers_) { |
| return std::nullopt; |
| } |
| |
| if (memory_ != V4L2_MEMORY_DMABUF) { |
| DVLOGF(1) << "Queue is not DMABUF"; |
| return std::nullopt; |
| } |
| |
| if (id.is_empty()) { |
| DVLOGF(1) << "Provided identifier was not valid"; |
| return std::nullopt; |
| } |
| |
| // If |id| has already been used in |buffers_|, then return that buffer. |
| // Otherwise use the next buffer from |free_buffers_indexes_|. |
| if (!base::Contains(free_buffers_indexes_, id)) { |
| if (free_buffers_indexes_.size() >= buffers_.size()) { |
| return std::nullopt; |
| } |
| // The value for |id| is simply the map size(): a poor man's way to have a |
| // monotonically increasing counter. |
| free_buffers_indexes_.emplace(id, free_buffers_indexes_.size()); |
| } |
| return GetFreeBuffer(free_buffers_indexes_[id]); |
| } |
| |
| bool V4L2Queue::QueueBuffer(struct v4l2_buffer* v4l2_buffer, |
| scoped_refptr<FrameResource> frame) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| V4L2ProcessingTrace(v4l2_buffer, /*start=*/true); |
| |
| int ret = ioctl_cb_.Run(VIDIOC_QBUF, v4l2_buffer); |
| if (ret) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocQbuf); |
| VPQLOGF(1) << "VIDIOC_QBUF failed"; |
| return false; |
| } |
| |
| const auto inserted = |
| queued_buffers_.emplace(v4l2_buffer->index, std::move(frame)); |
| DCHECK(inserted.second); |
| |
| schedule_poll_cb_.Run(); |
| |
| return true; |
| } |
| |
| std::pair<bool, V4L2ReadableBufferRef> V4L2Queue::DequeueBuffer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // No need to dequeue if no buffers queued. |
| if (QueuedBuffersCount() == 0) { |
| return std::make_pair(true, nullptr); |
| } |
| |
| if (!IsStreaming()) { |
| VQLOGF(1) << "Attempting to dequeue a buffer while not streaming."; |
| return std::make_pair(true, nullptr); |
| } |
| |
| struct v4l2_buffer v4l2_buffer; |
| memset(&v4l2_buffer, 0, sizeof(v4l2_buffer)); |
| // WARNING: do not change this to a vector or something smaller than |
| // VIDEO_MAX_PLANES (the maximum number of planes V4L2 supports). The |
| // element overhead is small and may avoid memory corruption bugs. |
| struct v4l2_plane planes[VIDEO_MAX_PLANES]; |
| memset(planes, 0, sizeof(planes)); |
| v4l2_buffer.type = type_; |
| v4l2_buffer.memory = memory_; |
| v4l2_buffer.m.planes = planes; |
| v4l2_buffer.length = planes_count_; |
| int ret = ioctl_cb_.Run(VIDIOC_DQBUF, &v4l2_buffer); |
| if (ret) { |
| // TODO(acourbot): we should not have to check for EPIPE as codec clients |
| // should not call this method after the last buffer is dequeued. |
| switch (errno) { |
| case EAGAIN: |
| case EPIPE: |
| // This is not an error so we'll need to continue polling but won't |
| // provide a buffer. |
| schedule_poll_cb_.Run(); |
| return std::make_pair(true, nullptr); |
| default: |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocDqbuf); |
| VPQLOGF(1) << "VIDIOC_DQBUF failed"; |
| return std::make_pair(false, nullptr); |
| } |
| } |
| |
| auto it = queued_buffers_.find(v4l2_buffer.index); |
| CHECK(it != queued_buffers_.end(), base::NotFatalUntil::M130); |
| scoped_refptr<FrameResource> queued_frame = std::move(it->second); |
| queued_buffers_.erase(it); |
| |
| V4L2ProcessingTrace(&v4l2_buffer, /*start=*/false); |
| |
| if (QueuedBuffersCount() > 0) { |
| schedule_poll_cb_.Run(); |
| } |
| |
| // See if we need to remove this from any of the secure buffer queue tracking. |
| for (auto& buf : secure_buffers_) { |
| std::erase_if(buf.queued_buffer_indexes, [v4l2_buffer](size_t idx) { |
| return idx == v4l2_buffer.index; |
| }); |
| } |
| |
| DCHECK(free_buffers_); |
| return std::make_pair(true, V4L2BufferRefFactory::CreateReadableRef( |
| v4l2_buffer, weak_this_factory_.GetWeakPtr(), |
| std::move(queued_frame))); |
| } |
| |
| bool V4L2Queue::IsStreaming() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return is_streaming_; |
| } |
| |
| bool V4L2Queue::Streamon() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (is_streaming_) { |
| return true; |
| } |
| |
| int arg = static_cast<int>(type_); |
| int ret = ioctl_cb_.Run(VIDIOC_STREAMON, &arg); |
| if (ret) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocStreamon); |
| VPQLOGF(1) << "VIDIOC_STREAMON failed"; |
| return false; |
| } |
| |
| is_streaming_ = true; |
| |
| return true; |
| } |
| |
| bool V4L2Queue::Streamoff() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // We do not check the value of IsStreaming(), because we may have queued |
| // buffers to the queue and wish to get them back - in such as case, we may |
| // need to do a VIDIOC_STREAMOFF on a stopped queue. |
| |
| int arg = static_cast<int>(type_); |
| int ret = ioctl_cb_.Run(VIDIOC_STREAMOFF, &arg); |
| if (ret) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocStreamoff); |
| VPQLOGF(1) << "VIDIOC_STREAMOFF failed"; |
| return false; |
| } |
| |
| for (const auto& it : queued_buffers_) { |
| DCHECK(free_buffers_); |
| free_buffers_->ReturnBuffer(it.first); |
| } |
| |
| for (auto& buf : secure_buffers_) { |
| buf.queued_buffer_indexes.clear(); |
| } |
| |
| queued_buffers_.clear(); |
| |
| is_streaming_ = false; |
| |
| return true; |
| } |
| |
| size_t V4L2Queue::AllocatedBuffersCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return buffers_.size(); |
| } |
| |
| size_t V4L2Queue::FreeBuffersCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return free_buffers_ ? free_buffers_->size() : 0; |
| } |
| |
| size_t V4L2Queue::QueuedBuffersCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return queued_buffers_.size(); |
| } |
| |
| #undef VDQLOGF |
| #undef VPQLOGF |
| #undef VQLOGF |
| |
| bool V4L2Queue::SupportsRequests() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return supports_requests_; |
| } |
| |
| std::optional<struct v4l2_format> V4L2Queue::SetModifierFormat( |
| uint64_t modifier, |
| const gfx::Size& size) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (DRM_FORMAT_MOD_QCOM_COMPRESSED == modifier) { |
| auto format = SetFormat(V4L2_PIX_FMT_QC08C, size, 0); |
| |
| if (!format) { |
| VPLOGF(1) << "Failed to set magic modifier format."; |
| } |
| return format; |
| } |
| return std::nullopt; |
| } |
| |
| bool V4L2Queue::SendStopCommand() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return SendCommand(V4L2_DEC_CMD_STOP); |
| } |
| |
| bool V4L2Queue::SendStartCommand() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return SendCommand(V4L2_DEC_CMD_START); |
| } |
| |
| bool V4L2Queue::SetBufferFdForSecureHandle(uint64_t secure_handle, |
| struct v4l2_buffer* v4l2_buffer) { |
| for (auto& buf : secure_buffers_) { |
| if (buf.secure_handle == secure_handle) { |
| if (!buf.owned_by_decoder_buffer) { |
| return false; |
| } |
| buf.queued_buffer_indexes.emplace_back(v4l2_buffer->index); |
| v4l2_buffer->m.planes[0].m.fd = buf.fd.get(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool V4L2Queue::SendCommand(__u32 command) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // TODO(mcasas): Restrict this to V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, after |
| // deprecating V4L2StatefulVideoDecoderBackend. |
| |
| struct v4l2_decoder_cmd cmd; |
| memset(&cmd, 0, sizeof(cmd)); // Must use memset() due to unions. |
| cmd.cmd = command; |
| const bool success = ioctl_cb_.Run(VIDIOC_DECODER_CMD, &cmd) == kIoctlOk; |
| PLOG_IF(ERROR, !success) << "Failed to issue command " << command |
| << " (V4L2_DEC_CMD_START: " << V4L2_DEC_CMD_START |
| << ", V4L2_DEC_CMD_STOP: " << V4L2_DEC_CMD_STOP |
| << ")"; |
| return success; |
| } |
| |
| void V4L2Queue::SecureBufferAllocated(base::ScopedFD secure_fd, |
| uint64_t secure_handle) { |
| CHECK(secure_fd.is_valid()); |
| CHECK(secure_handle); |
| secure_buffers_.emplace_back(secure_handle, std::move(secure_fd)); |
| } |
| |
| class V4L2Request { |
| public: |
| V4L2Request(const V4L2Request&) = delete; |
| V4L2Request& operator=(const V4L2Request&) = delete; |
| |
| // Apply the passed controls to the request. |
| bool ApplyCtrls(struct v4l2_ext_controls* ctrls); |
| // Apply the passed buffer to the request.. |
| bool ApplyQueueBuffer(struct v4l2_buffer* buffer); |
| // Submits the request to the driver. |
| bool Submit(); |
| // Indicates if the request has completed. |
| bool IsCompleted(); |
| // Waits for the request to complete for a determined timeout. Returns false |
| // if the request is not ready or other error. Default timeout is 500ms. |
| bool WaitForCompletion(int poll_timeout_ms = 500); |
| // Resets the request. |
| bool Reset(); |
| |
| private: |
| raw_ptr<V4L2RequestsQueue> request_queue_; |
| int ref_counter_ = 0; |
| base::ScopedFD request_fd_; |
| |
| friend class V4L2RequestsQueue; |
| V4L2Request(base::ScopedFD&& request_fd, V4L2RequestsQueue* request_queue) |
| : request_queue_(request_queue), request_fd_(std::move(request_fd)) {} |
| |
| friend class V4L2RequestRefBase; |
| // Increases the number of request references. |
| void IncRefCounter(); |
| // Decreases the number of request references. |
| // When the counters reaches zero, the request is returned to the queue. |
| int DecRefCounter(); |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| }; |
| |
| void V4L2Request::IncRefCounter() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ref_counter_++; |
| } |
| |
| int V4L2Request::DecRefCounter() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ref_counter_--; |
| |
| if (ref_counter_ < 1) { |
| request_queue_->ReturnRequest(this); |
| } |
| |
| return ref_counter_; |
| } |
| |
| bool V4L2Request::ApplyCtrls(struct v4l2_ext_controls* ctrls) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_NE(ctrls, nullptr); |
| |
| if (!request_fd_.is_valid()) { |
| VPLOGF(1) << "Invalid request"; |
| return false; |
| } |
| |
| ctrls->which = V4L2_CTRL_WHICH_REQUEST_VAL; |
| ctrls->request_fd = request_fd_.get(); |
| |
| return true; |
| } |
| |
| bool V4L2Request::ApplyQueueBuffer(struct v4l2_buffer* buffer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_NE(buffer, nullptr); |
| |
| if (!request_fd_.is_valid()) { |
| VPLOGF(1) << "Invalid request"; |
| return false; |
| } |
| |
| buffer->flags |= V4L2_BUF_FLAG_REQUEST_FD; |
| buffer->request_fd = request_fd_.get(); |
| |
| return true; |
| } |
| |
| bool V4L2Request::Submit() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!request_fd_.is_valid()) { |
| VPLOGF(1) << "No valid request file descriptor to submit request."; |
| return false; |
| } |
| |
| if (HANDLE_EINTR(ioctl(request_fd_.get(), MEDIA_REQUEST_IOC_QUEUE)) != 0) { |
| RecordMediaIoctlUMA(MediaIoctlRequests::kMediaRequestIocQueue); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool V4L2Request::IsCompleted() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return WaitForCompletion(0); |
| } |
| |
| bool V4L2Request::WaitForCompletion(int poll_timeout_ms) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!request_fd_.is_valid()) { |
| VPLOGF(1) << "Invalid request"; |
| return false; |
| } |
| |
| struct pollfd poll_fd = {request_fd_.get(), POLLPRI, 0}; |
| |
| // Poll the request to ensure its previous task is done |
| switch (poll(&poll_fd, 1, poll_timeout_ms)) { |
| case 1: |
| return true; |
| case 0: |
| // Not an error - we just timed out. |
| DVLOGF(4) << "Request poll(" << poll_timeout_ms << ") timed out"; |
| return false; |
| case -1: |
| VPLOGF(1) << "Failed to poll request"; |
| return false; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| bool V4L2Request::Reset() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!request_fd_.is_valid()) { |
| VPLOGF(1) << "Invalid request"; |
| return false; |
| } |
| |
| // Reinit the request to make sure we can use it for a new submission. |
| if (HANDLE_EINTR(ioctl(request_fd_.get(), MEDIA_REQUEST_IOC_REINIT)) < 0) { |
| RecordMediaIoctlUMA(MediaIoctlRequests::kMediaRequestIocReinit); |
| VPLOGF(1) << "Failed to reinit request."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| V4L2RequestRefBase::V4L2RequestRefBase(V4L2RequestRefBase&& req_base) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| request_ = req_base.request_; |
| req_base.request_ = nullptr; |
| } |
| |
| V4L2RequestRefBase::V4L2RequestRefBase(V4L2Request* request) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (request) { |
| request_ = request; |
| request_->IncRefCounter(); |
| } |
| } |
| |
| V4L2RequestRefBase::~V4L2RequestRefBase() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (request_) { |
| request_->DecRefCounter(); |
| } |
| } |
| |
| bool V4L2RequestRef::ApplyCtrls(struct v4l2_ext_controls* ctrls) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_NE(request_, nullptr); |
| |
| return request_->ApplyCtrls(ctrls); |
| } |
| |
| bool V4L2RequestRef::ApplyQueueBuffer(struct v4l2_buffer* buffer) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_NE(request_, nullptr); |
| |
| return request_->ApplyQueueBuffer(buffer); |
| } |
| |
| std::optional<V4L2SubmittedRequestRef> V4L2RequestRef::Submit() && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_NE(request_, nullptr); |
| |
| V4L2RequestRef self(std::move(*this)); |
| |
| if (!self.request_->Submit()) { |
| return std::nullopt; |
| } |
| |
| return V4L2SubmittedRequestRef(self.request_); |
| } |
| |
| bool V4L2SubmittedRequestRef::IsCompleted() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_NE(request_, nullptr); |
| |
| return request_->IsCompleted(); |
| } |
| |
| V4L2RequestsQueue::V4L2RequestsQueue(base::ScopedFD&& media_fd) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| media_fd_ = std::move(media_fd); |
| } |
| |
| V4L2RequestsQueue::~V4L2RequestsQueue() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| requests_.clear(); |
| media_fd_.reset(); |
| } |
| |
| std::optional<base::ScopedFD> V4L2RequestsQueue::CreateRequestFD() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| int request_fd; |
| int ret = HANDLE_EINTR( |
| ioctl(media_fd_.get(), MEDIA_IOC_REQUEST_ALLOC, &request_fd)); |
| if (ret < 0) { |
| RecordMediaIoctlUMA(MediaIoctlRequests::kMediaIocRequestAlloc); |
| VPLOGF(1) << "Failed to create request"; |
| return std::nullopt; |
| } |
| |
| return base::ScopedFD(request_fd); |
| } |
| |
| std::optional<V4L2RequestRef> V4L2RequestsQueue::GetFreeRequest() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| V4L2Request* request_ptr = |
| free_requests_.empty() ? nullptr : free_requests_.front(); |
| if (request_ptr && request_ptr->IsCompleted()) { |
| // Previous request is already completed, just recycle it. |
| free_requests_.pop(); |
| } else if (requests_.size() < kMaxNumRequests) { |
| // No request yet, or not completed, but we can allocate a new one. |
| auto request_fd = CreateRequestFD(); |
| if (!request_fd.has_value()) { |
| VLOGF(1) << "Error while creating a new request FD!"; |
| return std::nullopt; |
| } |
| // Not using std::make_unique because constructor is private. |
| std::unique_ptr<V4L2Request> request( |
| new V4L2Request(std::move(*request_fd), this)); |
| request_ptr = request.get(); |
| requests_.push_back(std::move(request)); |
| VLOGF(4) << "Allocated new request, total number: " << requests_.size(); |
| } else { |
| // Request is not completed and we have reached the maximum number. |
| // Wait for it to complete. |
| VLOGF(1) << "Waiting for request completion. This probably means a " |
| << "request is blocking."; |
| if (!request_ptr->WaitForCompletion()) { |
| VLOG(1) << "Timeout while waiting for request to complete."; |
| return std::nullopt; |
| } |
| free_requests_.pop(); |
| } |
| |
| DCHECK(request_ptr); |
| if (!request_ptr->Reset()) { |
| VPLOGF(1) << "Failed to reset request"; |
| return std::nullopt; |
| } |
| |
| return V4L2RequestRef(request_ptr); |
| } |
| |
| void V4L2RequestsQueue::ReturnRequest(V4L2Request* request) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(request); |
| |
| if (request) { |
| free_requests_.push(request); |
| } |
| } |
| |
| } // namespace media |