| // Copyright 2014 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. |
| // Note: ported from Chromium commit head: 2f13d62f0c0d |
| // Note: Added some missing defines that are only defined in newer kernel |
| // versions (e.g. V4L2_PIX_FMT_VP8_FRAME) |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "V4L2Device" |
| |
| #include <v4l2_codec2/common/V4L2Device.h> |
| |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <linux/media.h> |
| #include <linux/videodev2.h> |
| #include <poll.h> |
| #include <string.h> |
| #include <sys/eventfd.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| |
| #include <algorithm> |
| #include <mutex> |
| #include <set> |
| #include <sstream> |
| |
| #include <base/bind.h> |
| #include <base/numerics/safe_conversions.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/thread_annotations.h> |
| #include <utils/Log.h> |
| |
| #include <v4l2_codec2/common/Fourcc.h> |
| #include <v4l2_codec2/common/VideoPixelFormat.h> |
| |
| // VP8 parsed frames |
| #ifndef V4L2_PIX_FMT_VP8_FRAME |
| #define V4L2_PIX_FMT_VP8_FRAME v4l2_fourcc('V', 'P', '8', 'F') |
| #endif |
| |
| // VP9 parsed frames |
| #ifndef V4L2_PIX_FMT_VP9_FRAME |
| #define V4L2_PIX_FMT_VP9_FRAME v4l2_fourcc('V', 'P', '9', 'F') |
| #endif |
| |
| // H264 parsed slices |
| #ifndef V4L2_PIX_FMT_H264_SLICE |
| #define V4L2_PIX_FMT_H264_SLICE v4l2_fourcc('S', '2', '6', '4') |
| #endif |
| |
| // HEVC parsed slices |
| #ifndef V4L2_PIX_FMT_HEVC_SLICE |
| #define V4L2_PIX_FMT_HEVC_SLICE v4l2_fourcc('S', '2', '6', '5') |
| #endif |
| |
| namespace android { |
| |
| struct v4l2_format buildV4L2Format(const enum v4l2_buf_type type, uint32_t fourcc, |
| const ui::Size& size, size_t buffer_size, uint32_t stride) { |
| 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 = V4L2Device::getNumPlanesOfV4L2PixFmt(fourcc); |
| format.fmt.pix_mp.plane_fmt[0].sizeimage = buffer_size; |
| |
| // When the image format is planar the bytesperline value applies to the first plane and is |
| // divided by the same factor as the width field for the other planes. |
| format.fmt.pix_mp.plane_fmt[0].bytesperline = stride; |
| |
| return format; |
| } |
| |
| 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(scoped_refptr<V4L2Device> device, |
| enum v4l2_buf_type type, enum v4l2_memory memory, |
| const struct v4l2_format& format, size_t bufferId); |
| ~V4L2Buffer(); |
| |
| V4L2Buffer(const V4L2Buffer&) = delete; |
| V4L2Buffer& operator=(const V4L2Buffer&) = delete; |
| |
| void* getPlaneMapping(const size_t plane); |
| size_t getMemoryUsage() const; |
| const struct v4l2_buffer& v4l2_buffer() const { return mV4l2Buffer; } |
| |
| private: |
| V4L2Buffer(scoped_refptr<V4L2Device> device, enum v4l2_buf_type type, enum v4l2_memory memory, |
| const struct v4l2_format& format, size_t bufferId); |
| bool query(); |
| |
| scoped_refptr<V4L2Device> mDevice; |
| std::vector<void*> mPlaneMappings; |
| |
| // V4L2 data as queried by QUERYBUF. |
| struct v4l2_buffer mV4l2Buffer; |
| // WARNING: do not change this to a vector or something smaller than VIDEO_MAX_PLANES, otherwise |
| // the Tegra libv4l2 will write data beyond the number of allocated planes, resulting in memory |
| // corruption. |
| struct v4l2_plane mV4l2Planes[VIDEO_MAX_PLANES]; |
| |
| struct v4l2_format mFormat __attribute__((unused)); |
| }; |
| |
| std::unique_ptr<V4L2Buffer> V4L2Buffer::create(scoped_refptr<V4L2Device> device, |
| enum v4l2_buf_type type, enum v4l2_memory memory, |
| const struct v4l2_format& format, size_t bufferId) { |
| // Not using std::make_unique because constructor is private. |
| std::unique_ptr<V4L2Buffer> buffer(new V4L2Buffer(device, type, memory, format, bufferId)); |
| |
| if (!buffer->query()) return nullptr; |
| |
| return buffer; |
| } |
| |
| V4L2Buffer::V4L2Buffer(scoped_refptr<V4L2Device> device, enum v4l2_buf_type type, |
| enum v4l2_memory memory, const struct v4l2_format& format, size_t bufferId) |
| : mDevice(device), mFormat(format) { |
| ALOG_ASSERT(V4L2_TYPE_IS_MULTIPLANAR(type)); |
| ALOG_ASSERT(format.fmt.pix_mp.num_planes <= base::size(mV4l2Planes)); |
| |
| memset(mV4l2Planes, 0, sizeof(mV4l2Planes)); |
| memset(&mV4l2Buffer, 0, sizeof(mV4l2Buffer)); |
| mV4l2Buffer.m.planes = mV4l2Planes; |
| // Just in case we got more planes than we want. |
| mV4l2Buffer.length = |
| std::min(static_cast<size_t>(format.fmt.pix_mp.num_planes), base::size(mV4l2Planes)); |
| mV4l2Buffer.index = bufferId; |
| mV4l2Buffer.type = type; |
| mV4l2Buffer.memory = memory; |
| mV4l2Buffer.memory = V4L2_MEMORY_DMABUF; |
| mPlaneMappings.resize(mV4l2Buffer.length); |
| } |
| |
| V4L2Buffer::~V4L2Buffer() { |
| if (mV4l2Buffer.memory == V4L2_MEMORY_MMAP) { |
| for (size_t i = 0; i < mPlaneMappings.size(); i++) { |
| if (mPlaneMappings[i] != nullptr) { |
| mDevice->munmap(mPlaneMappings[i], mV4l2Buffer.m.planes[i].length); |
| } |
| } |
| } |
| } |
| |
| bool V4L2Buffer::query() { |
| int ret = mDevice->ioctl(VIDIOC_QUERYBUF, &mV4l2Buffer); |
| if (ret) { |
| ALOGE("VIDIOC_QUERYBUF failed"); |
| return false; |
| } |
| |
| DCHECK(mPlaneMappings.size() == mV4l2Buffer.length); |
| |
| return true; |
| } |
| |
| void* V4L2Buffer::getPlaneMapping(const size_t plane) { |
| if (plane >= mPlaneMappings.size()) { |
| ALOGE("Invalid plane %zu requested.", plane); |
| return nullptr; |
| } |
| |
| void* p = mPlaneMappings[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 (mV4l2Buffer.memory != V4L2_MEMORY_MMAP) { |
| ALOGE("Cannot create mapping on non-MMAP buffer"); |
| return nullptr; |
| } |
| |
| p = mDevice->mmap(NULL, mV4l2Buffer.m.planes[plane].length, PROT_READ | PROT_WRITE, MAP_SHARED, |
| mV4l2Buffer.m.planes[plane].m.mem_offset); |
| if (p == MAP_FAILED) { |
| ALOGE("mmap() failed: "); |
| return nullptr; |
| } |
| |
| mPlaneMappings[plane] = p; |
| return p; |
| } |
| |
| size_t V4L2Buffer::getMemoryUsage() const { |
| size_t usage = 0; |
| for (size_t i = 0; i < mV4l2Buffer.length; i++) { |
| usage += mV4l2Buffer.m.planes[i].length; |
| } |
| return usage; |
| } |
| |
| // 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 bufferId); |
| // 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 requestedBufferId); |
| // Number of buffers currently in this list. |
| size_t size() const; |
| |
| private: |
| friend class base::RefCountedThreadSafe<V4L2BuffersList>; |
| ~V4L2BuffersList() = default; |
| |
| mutable std::mutex mLock; |
| std::set<size_t> mFreeBuffers GUARDED_BY(mLock); |
| }; |
| |
| void V4L2BuffersList::returnBuffer(size_t bufferId) { |
| std::lock_guard<std::mutex> lock(mLock); |
| |
| auto inserted = mFreeBuffers.emplace(bufferId); |
| if (!inserted.second) { |
| ALOGE("Returning buffer failed"); |
| } |
| } |
| |
| std::optional<size_t> V4L2BuffersList::getFreeBuffer() { |
| std::lock_guard<std::mutex> lock(mLock); |
| |
| auto iter = mFreeBuffers.begin(); |
| if (iter == mFreeBuffers.end()) { |
| ALOGV("No free buffer available!"); |
| return std::nullopt; |
| } |
| |
| size_t bufferId = *iter; |
| mFreeBuffers.erase(iter); |
| |
| return bufferId; |
| } |
| |
| std::optional<size_t> V4L2BuffersList::getFreeBuffer(size_t requestedBufferId) { |
| std::lock_guard<std::mutex> lock(mLock); |
| |
| return (mFreeBuffers.erase(requestedBufferId) > 0) ? std::make_optional(requestedBufferId) |
| : std::nullopt; |
| } |
| |
| size_t V4L2BuffersList::size() const { |
| std::lock_guard<std::mutex> lock(mLock); |
| |
| return mFreeBuffers.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& v4l2Buffer, base::WeakPtr<V4L2Queue> queue); |
| ~V4L2BufferRefBase(); |
| |
| V4L2BufferRefBase(const V4L2BufferRefBase&) = delete; |
| V4L2BufferRefBase& operator=(const V4L2BufferRefBase&) = delete; |
| |
| bool queueBuffer(); |
| void* getPlaneMapping(const size_t plane); |
| |
| // 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 numFds) const; |
| |
| // Data from the buffer, that users can query and/or write. |
| struct v4l2_buffer mV4l2Buffer; |
| // WARNING: do not change this to a vector or something smaller than VIDEO_MAX_PLANES, otherwise |
| // the Tegra libv4l2 will write data beyond the number of allocated planes, resulting in memory |
| // corruption. |
| struct v4l2_plane mV4l2Planes[VIDEO_MAX_PLANES]; |
| |
| private: |
| size_t bufferId() const { return mV4l2Buffer.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> mQueue; |
| // Where to return this buffer if it goes out of scope without being queued. |
| scoped_refptr<V4L2BuffersList> mReturnTo; |
| bool queued = false; |
| |
| SEQUENCE_CHECKER(mSequenceChecker); |
| }; |
| |
| V4L2BufferRefBase::V4L2BufferRefBase(const struct v4l2_buffer& v4l2Buffer, |
| base::WeakPtr<V4L2Queue> queue) |
| : mQueue(std::move(queue)), mReturnTo(mQueue->mFreeBuffers) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(V4L2_TYPE_IS_MULTIPLANAR(v4l2Buffer.type)); |
| ALOG_ASSERT(v4l2Buffer.length <= base::size(mV4l2Planes)); |
| ALOG_ASSERT(mReturnTo); |
| |
| memcpy(&mV4l2Buffer, &v4l2Buffer, sizeof(mV4l2Buffer)); |
| memcpy(mV4l2Planes, v4l2Buffer.m.planes, sizeof(struct v4l2_plane) * v4l2Buffer.length); |
| mV4l2Buffer.m.planes = mV4l2Planes; |
| } |
| |
| V4L2BufferRefBase::~V4L2BufferRefBase() { |
| // We are the last reference and are only accessing the thread-safe mReturnTo, 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) mReturnTo->returnBuffer(bufferId()); |
| } |
| |
| bool V4L2BufferRefBase::queueBuffer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| if (!mQueue) return false; |
| |
| queued = mQueue->queueBuffer(&mV4l2Buffer); |
| |
| return queued; |
| } |
| |
| void* V4L2BufferRefBase::getPlaneMapping(const size_t plane) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| if (!mQueue) return nullptr; |
| |
| return mQueue->mBuffers[bufferId()]->getPlaneMapping(plane); |
| } |
| |
| bool V4L2BufferRefBase::checkNumFDsForFormat(const size_t numFds) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| if (!mQueue) return false; |
| |
| // We have not used SetFormat(), assume this is ok. |
| // Hopefully we standardize SetFormat() in the future. |
| if (!mQueue->mCurrentFormat) return true; |
| |
| const size_t requiredFds = mQueue->mCurrentFormat->fmt.pix_mp.num_planes; |
| // Sanity check. |
| ALOG_ASSERT(mV4l2Buffer.length == requiredFds); |
| if (numFds < requiredFds) { |
| ALOGE("Insufficient number of FDs given for the current format. " |
| "%zu provided, %zu required.", |
| numFds, requiredFds); |
| return false; |
| } |
| |
| const auto* planes = mV4l2Buffer.m.planes; |
| for (size_t i = mV4l2Buffer.length - 1; i >= numFds; --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) { |
| ALOGE("Additional dmabuf fds point to a new buffer."); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| V4L2WritableBufferRef::V4L2WritableBufferRef(const struct v4l2_buffer& v4l2Buffer, |
| base::WeakPtr<V4L2Queue> queue) |
| : mBufferData(std::make_unique<V4L2BufferRefBase>(v4l2Buffer, std::move(queue))) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| } |
| |
| V4L2WritableBufferRef::V4L2WritableBufferRef(V4L2WritableBufferRef&& other) |
| : mBufferData(std::move(other.mBufferData)) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(other.mSequenceChecker); |
| } |
| |
| V4L2WritableBufferRef::~V4L2WritableBufferRef() { |
| // Only valid references should be sequence-checked |
| if (mBufferData) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| } |
| } |
| |
| V4L2WritableBufferRef& V4L2WritableBufferRef::operator=(V4L2WritableBufferRef&& other) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(other.mSequenceChecker); |
| |
| if (this == &other) return *this; |
| |
| mBufferData = std::move(other.mBufferData); |
| |
| return *this; |
| } |
| |
| enum v4l2_memory V4L2WritableBufferRef::memory() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return static_cast<enum v4l2_memory>(mBufferData->mV4l2Buffer.memory); |
| } |
| |
| bool V4L2WritableBufferRef::doQueue() && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| bool queued = mBufferData->queueBuffer(); |
| |
| // Clear our own reference. |
| mBufferData.reset(); |
| |
| return queued; |
| } |
| |
| bool V4L2WritableBufferRef::queueMMap() && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| // Move ourselves so our data gets freed no matter when we return |
| V4L2WritableBufferRef self(std::move(*this)); |
| |
| if (self.memory() != V4L2_MEMORY_MMAP) { |
| ALOGE("Called on invalid buffer type!"); |
| return false; |
| } |
| |
| return std::move(self).doQueue(); |
| } |
| |
| bool V4L2WritableBufferRef::queueUserPtr(const std::vector<void*>& ptrs) && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| // Move ourselves so our data gets freed no matter when we return |
| V4L2WritableBufferRef self(std::move(*this)); |
| |
| if (self.memory() != V4L2_MEMORY_USERPTR) { |
| ALOGE("Called on invalid buffer type!"); |
| return false; |
| } |
| |
| if (ptrs.size() != self.planesCount()) { |
| ALOGE("Provided %zu pointers while we require %u.", ptrs.size(), |
| self.mBufferData->mV4l2Buffer.length); |
| return false; |
| } |
| |
| for (size_t i = 0; i < ptrs.size(); i++) { |
| self.mBufferData->mV4l2Buffer.m.planes[i].m.userptr = |
| reinterpret_cast<unsigned long>(ptrs[i]); |
| } |
| |
| return std::move(self).doQueue(); |
| } |
| |
| bool V4L2WritableBufferRef::queueDMABuf(const std::vector<int>& fds) && { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| // Move ourselves so our data gets freed no matter when we return |
| V4L2WritableBufferRef self(std::move(*this)); |
| |
| if (self.memory() != V4L2_MEMORY_DMABUF) { |
| ALOGE("Called on invalid buffer type!"); |
| return false; |
| } |
| |
| if (!self.mBufferData->checkNumFDsForFormat(fds.size())) return false; |
| |
| size_t numPlanes = self.planesCount(); |
| for (size_t i = 0; i < numPlanes; i++) self.mBufferData->mV4l2Buffer.m.planes[i].m.fd = fds[i]; |
| |
| return std::move(self).doQueue(); |
| } |
| |
| size_t V4L2WritableBufferRef::planesCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return mBufferData->mV4l2Buffer.length; |
| } |
| |
| size_t V4L2WritableBufferRef::getPlaneSize(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| if (plane >= planesCount()) { |
| ALOGE("Invalid plane %zu requested.", plane); |
| return 0; |
| } |
| |
| return mBufferData->mV4l2Buffer.m.planes[plane].length; |
| } |
| |
| void V4L2WritableBufferRef::setPlaneSize(const size_t plane, const size_t size) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| enum v4l2_memory mem = memory(); |
| if (mem == V4L2_MEMORY_MMAP) { |
| ALOG_ASSERT(mBufferData->mV4l2Buffer.m.planes[plane].length == size); |
| return; |
| } |
| ALOG_ASSERT(mem == V4L2_MEMORY_USERPTR || mem == V4L2_MEMORY_DMABUF); |
| |
| if (plane >= planesCount()) { |
| ALOGE("Invalid plane %zu requested.", plane); |
| return; |
| } |
| |
| mBufferData->mV4l2Buffer.m.planes[plane].length = size; |
| } |
| |
| void* V4L2WritableBufferRef::getPlaneMapping(const size_t plane) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return mBufferData->getPlaneMapping(plane); |
| } |
| |
| void V4L2WritableBufferRef::setTimeStamp(const struct timeval& timestamp) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| mBufferData->mV4l2Buffer.timestamp = timestamp; |
| } |
| |
| const struct timeval& V4L2WritableBufferRef::getTimeStamp() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return mBufferData->mV4l2Buffer.timestamp; |
| } |
| |
| void V4L2WritableBufferRef::setPlaneBytesUsed(const size_t plane, const size_t bytesUsed) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| if (plane >= planesCount()) { |
| ALOGE("Invalid plane %zu requested.", plane); |
| return; |
| } |
| |
| if (bytesUsed > getPlaneSize(plane)) { |
| ALOGE("Set bytes used %zu larger than plane size %zu.", bytesUsed, getPlaneSize(plane)); |
| return; |
| } |
| |
| mBufferData->mV4l2Buffer.m.planes[plane].bytesused = bytesUsed; |
| } |
| |
| size_t V4L2WritableBufferRef::getPlaneBytesUsed(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| if (plane >= planesCount()) { |
| ALOGE("Invalid plane %zu requested.", plane); |
| return 0; |
| } |
| |
| return mBufferData->mV4l2Buffer.m.planes[plane].bytesused; |
| } |
| |
| void V4L2WritableBufferRef::setPlaneDataOffset(const size_t plane, const size_t dataOffset) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| if (plane >= planesCount()) { |
| ALOGE("Invalid plane %zu requested.", plane); |
| return; |
| } |
| |
| mBufferData->mV4l2Buffer.m.planes[plane].data_offset = dataOffset; |
| } |
| |
| size_t V4L2WritableBufferRef::bufferId() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return mBufferData->mV4l2Buffer.index; |
| } |
| |
| V4L2ReadableBuffer::V4L2ReadableBuffer(const struct v4l2_buffer& v4l2Buffer, |
| base::WeakPtr<V4L2Queue> queue) |
| : mBufferData(std::make_unique<V4L2BufferRefBase>(v4l2Buffer, std::move(queue))) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| } |
| |
| 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. |
| ALOG_ASSERT(mBufferData); |
| } |
| |
| bool V4L2ReadableBuffer::isLast() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return mBufferData->mV4l2Buffer.flags & V4L2_BUF_FLAG_LAST; |
| } |
| |
| bool V4L2ReadableBuffer::isKeyframe() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return mBufferData->mV4l2Buffer.flags & V4L2_BUF_FLAG_KEYFRAME; |
| } |
| |
| struct timeval V4L2ReadableBuffer::getTimeStamp() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return mBufferData->mV4l2Buffer.timestamp; |
| } |
| |
| size_t V4L2ReadableBuffer::planesCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return mBufferData->mV4l2Buffer.length; |
| } |
| |
| const void* V4L2ReadableBuffer::getPlaneMapping(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| DCHECK(mBufferData); |
| |
| return mBufferData->getPlaneMapping(plane); |
| } |
| |
| size_t V4L2ReadableBuffer::getPlaneBytesUsed(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| if (plane >= planesCount()) { |
| ALOGE("Invalid plane %zu requested.", plane); |
| return 0; |
| } |
| |
| return mBufferData->mV4l2Planes[plane].bytesused; |
| } |
| |
| size_t V4L2ReadableBuffer::getPlaneDataOffset(const size_t plane) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| if (plane >= planesCount()) { |
| ALOGE("Invalid plane %zu requested.", plane); |
| return 0; |
| } |
| |
| return mBufferData->mV4l2Planes[plane].data_offset; |
| } |
| |
| size_t V4L2ReadableBuffer::bufferId() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(mBufferData); |
| |
| return mBufferData->mV4l2Buffer.index; |
| } |
| |
| // 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& v4l2Buffer, |
| base::WeakPtr<V4L2Queue> queue) { |
| return V4L2WritableBufferRef(v4l2Buffer, std::move(queue)); |
| } |
| |
| static V4L2ReadableBufferRef CreateReadableRef(const struct v4l2_buffer& v4l2Buffer, |
| base::WeakPtr<V4L2Queue> queue) { |
| return new V4L2ReadableBuffer(v4l2Buffer, std::move(queue)); |
| } |
| }; |
| |
| //// Helper macros that print the queue type with logs. |
| #define ALOGEQ(fmt, ...) ALOGE("(%s)" fmt, V4L2Device::v4L2BufferTypeToString(mType), ##__VA_ARGS__) |
| #define ALOGVQ(fmt, ...) ALOGD("(%s)" fmt, V4L2Device::v4L2BufferTypeToString(mType), ##__VA_ARGS__) |
| |
| V4L2Queue::V4L2Queue(scoped_refptr<V4L2Device> dev, enum v4l2_buf_type type, |
| base::OnceClosure destroyCb) |
| : mType(type), mDevice(dev), mDestroyCb(std::move(destroyCb)) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| } |
| |
| V4L2Queue::~V4L2Queue() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| if (mIsStreaming) { |
| ALOGEQ("Queue is still streaming, trying to stop it..."); |
| streamoff(); |
| } |
| |
| ALOG_ASSERT(mQueuedBuffers.empty()); |
| ALOG_ASSERT(!mFreeBuffers); |
| |
| if (!mBuffers.empty()) { |
| ALOGEQ("Buffers are still allocated, trying to deallocate them..."); |
| deallocateBuffers(); |
| } |
| |
| std::move(mDestroyCb).Run(); |
| } |
| |
| std::optional<struct v4l2_format> V4L2Queue::setFormat(uint32_t fourcc, const ui::Size& size, |
| size_t bufferSize, uint32_t stride) { |
| struct v4l2_format format = buildV4L2Format(mType, fourcc, size, bufferSize, stride); |
| if (mDevice->ioctl(VIDIOC_S_FMT, &format) != 0 || format.fmt.pix_mp.pixelformat != fourcc) { |
| ALOGEQ("Failed to set format (format_fourcc=0x%" PRIx32 ")", fourcc); |
| return std::nullopt; |
| } |
| |
| mCurrentFormat = format; |
| return mCurrentFormat; |
| } |
| |
| std::optional<struct v4l2_format> V4L2Queue::tryFormat(uint32_t fourcc, const ui::Size& size, |
| size_t bufferSize) { |
| struct v4l2_format format = buildV4L2Format(mType, fourcc, size, bufferSize, 0); |
| if (mDevice->ioctl(VIDIOC_TRY_FMT, &format) != 0 || format.fmt.pix_mp.pixelformat != fourcc) { |
| ALOGEQ("Tried format not supported (format_fourcc=0x%" PRIx32 ")", fourcc); |
| return std::nullopt; |
| } |
| |
| return format; |
| } |
| |
| std::pair<std::optional<struct v4l2_format>, int> V4L2Queue::getFormat() { |
| struct v4l2_format format; |
| memset(&format, 0, sizeof(format)); |
| format.type = mType; |
| if (mDevice->ioctl(VIDIOC_G_FMT, &format) != 0) { |
| ALOGEQ("Failed to get format"); |
| return std::make_pair(std::nullopt, errno); |
| } |
| |
| return std::make_pair(format, 0); |
| } |
| |
| size_t V4L2Queue::allocateBuffers(size_t count, enum v4l2_memory memory) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| ALOG_ASSERT(!mFreeBuffers); |
| ALOG_ASSERT(mQueuedBuffers.size() == 0u); |
| |
| if (isStreaming()) { |
| ALOGEQ("Cannot allocate buffers while streaming."); |
| return 0; |
| } |
| |
| if (mBuffers.size() != 0) { |
| ALOGEQ("Cannot allocate new buffers while others are still allocated."); |
| return 0; |
| } |
| |
| if (count == 0) { |
| ALOGEQ("Attempting to allocate 0 buffers."); |
| return 0; |
| } |
| |
| // First query the number of planes in the buffers we are about to request. This should not be |
| // required, but Tegra's VIDIOC_QUERYBUF will fail on output buffers if the number of specified |
| // planes does not exactly match the format. |
| struct v4l2_format format = {.type = mType}; |
| int ret = mDevice->ioctl(VIDIOC_G_FMT, &format); |
| if (ret) { |
| ALOGEQ("VIDIOC_G_FMT failed"); |
| return 0; |
| } |
| mPlanesCount = format.fmt.pix_mp.num_planes; |
| ALOG_ASSERT(mPlanesCount <= static_cast<size_t>(VIDEO_MAX_PLANES)); |
| |
| struct v4l2_requestbuffers reqbufs; |
| memset(&reqbufs, 0, sizeof(reqbufs)); |
| reqbufs.count = count; |
| reqbufs.type = mType; |
| reqbufs.memory = memory; |
| ALOGVQ("Requesting %zu buffers.", count); |
| |
| ret = mDevice->ioctl(VIDIOC_REQBUFS, &reqbufs); |
| if (ret) { |
| ALOGEQ("VIDIOC_REQBUFS failed"); |
| return 0; |
| } |
| ALOGVQ("Queue %u: got %u buffers.", mType, reqbufs.count); |
| |
| mMemory = memory; |
| |
| mFreeBuffers = new V4L2BuffersList(); |
| |
| // Now query all buffer information. |
| for (size_t i = 0; i < reqbufs.count; i++) { |
| auto buffer = V4L2Buffer::create(mDevice, mType, mMemory, format, i); |
| |
| if (!buffer) { |
| deallocateBuffers(); |
| |
| return 0; |
| } |
| |
| mBuffers.emplace_back(std::move(buffer)); |
| mFreeBuffers->returnBuffer(i); |
| } |
| |
| ALOG_ASSERT(mFreeBuffers); |
| ALOG_ASSERT(mFreeBuffers->size() == mBuffers.size()); |
| ALOG_ASSERT(mQueuedBuffers.size() == 0u); |
| |
| return mBuffers.size(); |
| } |
| |
| bool V4L2Queue::deallocateBuffers() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| if (isStreaming()) { |
| ALOGEQ("Cannot deallocate buffers while streaming."); |
| return false; |
| } |
| |
| if (mBuffers.size() == 0) return true; |
| |
| mWeakThisFactory.InvalidateWeakPtrs(); |
| mBuffers.clear(); |
| mFreeBuffers = nullptr; |
| |
| // Free all buffers. |
| struct v4l2_requestbuffers reqbufs; |
| memset(&reqbufs, 0, sizeof(reqbufs)); |
| reqbufs.count = 0; |
| reqbufs.type = mType; |
| reqbufs.memory = mMemory; |
| |
| int ret = mDevice->ioctl(VIDIOC_REQBUFS, &reqbufs); |
| if (ret) { |
| ALOGEQ("VIDIOC_REQBUFS failed"); |
| return false; |
| } |
| |
| ALOG_ASSERT(!mFreeBuffers); |
| ALOG_ASSERT(mQueuedBuffers.size() == 0u); |
| |
| return true; |
| } |
| |
| size_t V4L2Queue::getMemoryUsage() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| size_t usage = 0; |
| for (const auto& buf : mBuffers) { |
| usage += buf->getMemoryUsage(); |
| } |
| return usage; |
| } |
| |
| v4l2_memory V4L2Queue::getMemoryType() const { |
| return mMemory; |
| } |
| |
| std::optional<V4L2WritableBufferRef> V4L2Queue::getFreeBuffer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| // No buffers allocated at the moment? |
| if (!mFreeBuffers) return std::nullopt; |
| |
| auto bufferId = mFreeBuffers->getFreeBuffer(); |
| if (!bufferId.has_value()) return std::nullopt; |
| |
| return V4L2BufferRefFactory::CreateWritableRef(mBuffers[bufferId.value()]->v4l2_buffer(), |
| mWeakThisFactory.GetWeakPtr()); |
| } |
| |
| std::optional<V4L2WritableBufferRef> V4L2Queue::getFreeBuffer(size_t requestedBufferIid) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| // No buffers allocated at the moment? |
| if (!mFreeBuffers) return std::nullopt; |
| |
| auto bufferId = mFreeBuffers->getFreeBuffer(requestedBufferIid); |
| if (!bufferId.has_value()) return std::nullopt; |
| |
| return V4L2BufferRefFactory::CreateWritableRef(mBuffers[bufferId.value()]->v4l2_buffer(), |
| mWeakThisFactory.GetWeakPtr()); |
| } |
| |
| bool V4L2Queue::queueBuffer(struct v4l2_buffer* v4l2Buffer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| int ret = mDevice->ioctl(VIDIOC_QBUF, v4l2Buffer); |
| if (ret) { |
| ALOGEQ("VIDIOC_QBUF failed"); |
| return false; |
| } |
| |
| auto inserted = mQueuedBuffers.emplace(v4l2Buffer->index); |
| if (!inserted.second) { |
| ALOGE("Queuing buffer failed"); |
| return false; |
| } |
| |
| mDevice->schedulePoll(); |
| |
| return true; |
| } |
| |
| std::pair<bool, V4L2ReadableBufferRef> V4L2Queue::dequeueBuffer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| // No need to dequeue if no buffers queued. |
| if (queuedBuffersCount() == 0) return std::make_pair(true, nullptr); |
| |
| if (!isStreaming()) { |
| ALOGEQ("Attempting to dequeue a buffer while not streaming."); |
| return std::make_pair(true, nullptr); |
| } |
| |
| struct v4l2_buffer v4l2Buffer; |
| memset(&v4l2Buffer, 0, sizeof(v4l2Buffer)); |
| // WARNING: do not change this to a vector or something smaller than VIDEO_MAX_PLANES, otherwise |
| // the Tegra libv4l2 will write data beyond the number of allocated planes, resulting in memory |
| // corruption. |
| struct v4l2_plane planes[VIDEO_MAX_PLANES]; |
| memset(planes, 0, sizeof(planes)); |
| v4l2Buffer.type = mType; |
| v4l2Buffer.memory = mMemory; |
| v4l2Buffer.m.planes = planes; |
| v4l2Buffer.length = mPlanesCount; |
| int ret = mDevice->ioctl(VIDIOC_DQBUF, &v4l2Buffer); |
| 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. |
| mDevice->schedulePoll(); |
| return std::make_pair(true, nullptr); |
| default: |
| ALOGEQ("VIDIOC_DQBUF failed"); |
| return std::make_pair(false, nullptr); |
| } |
| } |
| |
| auto it = mQueuedBuffers.find(v4l2Buffer.index); |
| ALOG_ASSERT(it != mQueuedBuffers.end()); |
| mQueuedBuffers.erase(*it); |
| |
| if (queuedBuffersCount() > 0) mDevice->schedulePoll(); |
| |
| ALOG_ASSERT(mFreeBuffers); |
| return std::make_pair(true, V4L2BufferRefFactory::CreateReadableRef( |
| v4l2Buffer, mWeakThisFactory.GetWeakPtr())); |
| } |
| |
| bool V4L2Queue::isStreaming() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| return mIsStreaming; |
| } |
| |
| bool V4L2Queue::streamon() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| if (mIsStreaming) return true; |
| |
| int arg = static_cast<int>(mType); |
| int ret = mDevice->ioctl(VIDIOC_STREAMON, &arg); |
| if (ret) { |
| ALOGEQ("VIDIOC_STREAMON failed"); |
| return false; |
| } |
| |
| mIsStreaming = true; |
| |
| return true; |
| } |
| |
| bool V4L2Queue::streamoff() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| // 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>(mType); |
| int ret = mDevice->ioctl(VIDIOC_STREAMOFF, &arg); |
| if (ret) { |
| ALOGEQ("VIDIOC_STREAMOFF failed"); |
| return false; |
| } |
| |
| for (const auto& bufferId : mQueuedBuffers) { |
| ALOG_ASSERT(mFreeBuffers); |
| mFreeBuffers->returnBuffer(bufferId); |
| } |
| |
| mQueuedBuffers.clear(); |
| |
| mIsStreaming = false; |
| |
| return true; |
| } |
| |
| size_t V4L2Queue::allocatedBuffersCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| return mBuffers.size(); |
| } |
| |
| size_t V4L2Queue::freeBuffersCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| return mFreeBuffers ? mFreeBuffers->size() : 0; |
| } |
| |
| size_t V4L2Queue::queuedBuffersCount() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mSequenceChecker); |
| |
| return mQueuedBuffers.size(); |
| } |
| |
| #undef ALOGEQ |
| #undef ALOGVQ |
| |
| // This class is used to expose V4L2Queue's constructor to this module. This is to ensure that |
| // nobody else can create instances of it. |
| class V4L2QueueFactory { |
| public: |
| static scoped_refptr<V4L2Queue> createQueue(scoped_refptr<V4L2Device> dev, |
| enum v4l2_buf_type type, |
| base::OnceClosure destroyCb) { |
| return new V4L2Queue(std::move(dev), type, std::move(destroyCb)); |
| } |
| }; |
| |
| V4L2Device::V4L2Device() { |
| DETACH_FROM_SEQUENCE(mClientSequenceChecker); |
| } |
| |
| V4L2Device::~V4L2Device() { |
| closeDevice(); |
| } |
| |
| scoped_refptr<V4L2Queue> V4L2Device::getQueue(enum v4l2_buf_type type) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mClientSequenceChecker); |
| |
| switch (type) { |
| // Supported queue types. |
| case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: |
| case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: |
| break; |
| default: |
| ALOGE("Unsupported V4L2 queue type: %u", type); |
| return nullptr; |
| } |
| |
| // TODO(acourbot): we should instead query the device for available queues, and allocate them |
| // accordingly. This will do for now though. |
| auto it = mQueues.find(type); |
| if (it != mQueues.end()) return scoped_refptr<V4L2Queue>(it->second); |
| |
| scoped_refptr<V4L2Queue> queue = V4L2QueueFactory::createQueue( |
| this, type, base::BindOnce(&V4L2Device::onQueueDestroyed, this, type)); |
| |
| mQueues[type] = queue.get(); |
| return queue; |
| } |
| |
| void V4L2Device::onQueueDestroyed(v4l2_buf_type bufType) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mClientSequenceChecker); |
| |
| auto it = mQueues.find(bufType); |
| ALOG_ASSERT(it != mQueues.end()); |
| mQueues.erase(it); |
| } |
| |
| // static |
| scoped_refptr<V4L2Device> V4L2Device::create() { |
| ALOGV("%s()", __func__); |
| return scoped_refptr<V4L2Device>(new V4L2Device()); |
| } |
| |
| bool V4L2Device::open(Type type, uint32_t v4l2PixFmt) { |
| ALOGV("%s()", __func__); |
| |
| std::string path = getDevicePathFor(type, v4l2PixFmt); |
| |
| if (path.empty()) { |
| ALOGE("No devices supporting %s for type: %u", fourccToString(v4l2PixFmt).c_str(), |
| static_cast<uint32_t>(type)); |
| return false; |
| } |
| |
| if (!openDevicePath(path, type)) { |
| ALOGE("Failed opening %s", path.c_str()); |
| return false; |
| } |
| |
| mDevicePollInterruptFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); |
| if (!mDevicePollInterruptFd.is_valid()) { |
| ALOGE("Failed creating a poll interrupt fd"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int V4L2Device::ioctl(int request, void* arg) { |
| ALOG_ASSERT(mDeviceFd.is_valid()); |
| return HANDLE_EINTR(::ioctl(mDeviceFd.get(), request, arg)); |
| } |
| |
| bool V4L2Device::poll(bool pollDevice, bool* eventPending) { |
| struct pollfd pollfds[2]; |
| nfds_t nfds; |
| int pollfd = -1; |
| |
| pollfds[0].fd = mDevicePollInterruptFd.get(); |
| pollfds[0].events = POLLIN | POLLERR; |
| nfds = 1; |
| |
| if (pollDevice) { |
| ALOGV("adding device fd to poll() set"); |
| pollfds[nfds].fd = mDeviceFd.get(); |
| pollfds[nfds].events = POLLIN | POLLOUT | POLLERR | POLLPRI; |
| pollfd = nfds; |
| nfds++; |
| } |
| |
| if (HANDLE_EINTR(::poll(pollfds, nfds, -1)) == -1) { |
| ALOGE("poll() failed"); |
| return false; |
| } |
| *eventPending = (pollfd != -1 && pollfds[pollfd].revents & POLLPRI); |
| return true; |
| } |
| |
| void* V4L2Device::mmap(void* addr, unsigned int len, int prot, int flags, unsigned int offset) { |
| DCHECK(mDeviceFd.is_valid()); |
| return ::mmap(addr, len, prot, flags, mDeviceFd.get(), offset); |
| } |
| |
| void V4L2Device::munmap(void* addr, unsigned int len) { |
| ::munmap(addr, len); |
| } |
| |
| bool V4L2Device::setDevicePollInterrupt() { |
| ALOGV("%s()", __func__); |
| |
| const uint64_t buf = 1; |
| if (HANDLE_EINTR(write(mDevicePollInterruptFd.get(), &buf, sizeof(buf))) == -1) { |
| ALOGE("write() failed"); |
| return false; |
| } |
| return true; |
| } |
| |
| bool V4L2Device::clearDevicePollInterrupt() { |
| ALOGV("%s()", __func__); |
| |
| uint64_t buf; |
| if (HANDLE_EINTR(read(mDevicePollInterruptFd.get(), &buf, sizeof(buf))) == -1) { |
| if (errno == EAGAIN) { |
| // No interrupt flag set, and we're reading nonblocking. Not an error. |
| return true; |
| } else { |
| ALOGE("read() failed"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| std::vector<base::ScopedFD> V4L2Device::getDmabufsForV4L2Buffer(int index, size_t numPlanes, |
| enum v4l2_buf_type bufType) { |
| ALOGV("%s()", __func__); |
| ALOG_ASSERT(V4L2_TYPE_IS_MULTIPLANAR(bufType)); |
| |
| std::vector<base::ScopedFD> dmabufFds; |
| for (size_t i = 0; i < numPlanes; ++i) { |
| struct v4l2_exportbuffer expbuf; |
| memset(&expbuf, 0, sizeof(expbuf)); |
| expbuf.type = bufType; |
| expbuf.index = index; |
| expbuf.plane = i; |
| expbuf.flags = O_CLOEXEC; |
| if (ioctl(VIDIOC_EXPBUF, &expbuf) != 0) { |
| dmabufFds.clear(); |
| break; |
| } |
| |
| dmabufFds.push_back(base::ScopedFD(expbuf.fd)); |
| } |
| |
| return dmabufFds; |
| } |
| |
| std::vector<uint32_t> V4L2Device::preferredInputFormat(Type type) { |
| if (type == Type::kEncoder) return {V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_NV12}; |
| |
| return {}; |
| } |
| |
| // static |
| uint32_t V4L2Device::C2ProfileToV4L2PixFmt(C2Config::profile_t profile, bool sliceBased) { |
| if (profile >= C2Config::PROFILE_AVC_BASELINE && |
| profile <= C2Config::PROFILE_AVC_ENHANCED_MULTIVIEW_DEPTH_HIGH) { |
| if (sliceBased) { |
| return V4L2_PIX_FMT_H264_SLICE; |
| } else { |
| return V4L2_PIX_FMT_H264; |
| } |
| } else if (profile >= C2Config::PROFILE_VP8_0 && profile <= C2Config::PROFILE_VP8_3) { |
| if (sliceBased) { |
| return V4L2_PIX_FMT_VP8_FRAME; |
| } else { |
| return V4L2_PIX_FMT_VP8; |
| } |
| } else if (profile >= C2Config::PROFILE_VP9_0 && profile <= C2Config::PROFILE_VP9_3) { |
| if (sliceBased) { |
| return V4L2_PIX_FMT_VP9_FRAME; |
| } else { |
| return V4L2_PIX_FMT_VP9; |
| } |
| } else if (profile >= C2Config::PROFILE_HEVC_MAIN && |
| profile <= C2Config::PROFILE_HEVC_3D_MAIN) { |
| if (sliceBased) { |
| return V4L2_PIX_FMT_HEVC_SLICE; |
| } else { |
| return V4L2_PIX_FMT_HEVC; |
| } |
| } else { |
| ALOGE("Unknown profile: %s", profileToString(profile)); |
| return 0; |
| } |
| } |
| |
| // static |
| C2Config::profile_t V4L2Device::v4L2ProfileToC2Profile(VideoCodec codec, uint32_t profile) { |
| switch (codec) { |
| case VideoCodec::H264: |
| switch (profile) { |
| case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: |
| case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE: |
| return C2Config::PROFILE_AVC_BASELINE; |
| case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: |
| return C2Config::PROFILE_AVC_MAIN; |
| case V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED: |
| return C2Config::PROFILE_AVC_EXTENDED; |
| case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: |
| return C2Config::PROFILE_AVC_HIGH; |
| } |
| break; |
| case VideoCodec::VP8: |
| switch (profile) { |
| case V4L2_MPEG_VIDEO_VP8_PROFILE_0: |
| return C2Config::PROFILE_VP8_0; |
| case V4L2_MPEG_VIDEO_VP8_PROFILE_1: |
| return C2Config::PROFILE_VP8_1; |
| case V4L2_MPEG_VIDEO_VP8_PROFILE_2: |
| return C2Config::PROFILE_VP8_2; |
| case V4L2_MPEG_VIDEO_VP8_PROFILE_3: |
| return C2Config::PROFILE_VP8_3; |
| } |
| break; |
| case VideoCodec::VP9: |
| switch (profile) { |
| case V4L2_MPEG_VIDEO_VP9_PROFILE_0: |
| return C2Config::PROFILE_VP9_0; |
| case V4L2_MPEG_VIDEO_VP9_PROFILE_1: |
| return C2Config::PROFILE_VP9_1; |
| case V4L2_MPEG_VIDEO_VP9_PROFILE_2: |
| return C2Config::PROFILE_VP9_2; |
| case V4L2_MPEG_VIDEO_VP9_PROFILE_3: |
| return C2Config::PROFILE_VP9_3; |
| } |
| break; |
| case VideoCodec::HEVC: |
| switch (profile) { |
| case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN: |
| return C2Config::PROFILE_HEVC_MAIN; |
| case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE: |
| return C2Config::PROFILE_HEVC_MAIN_STILL; |
| case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10: |
| return C2Config::PROFILE_HEVC_MAIN_10; |
| } |
| break; |
| default: |
| ALOGE("Unknown codec: %u", codec); |
| } |
| ALOGE("Unknown profile: %u", profile); |
| return C2Config::PROFILE_UNUSED; |
| } |
| |
| std::vector<C2Config::profile_t> V4L2Device::v4L2PixFmtToC2Profiles(uint32_t pixFmt, |
| bool /*isEncoder*/) { |
| auto getSupportedProfiles = [this](VideoCodec codec, |
| std::vector<C2Config::profile_t>* profiles) { |
| uint32_t queryId = 0; |
| switch (codec) { |
| case VideoCodec::H264: |
| queryId = V4L2_CID_MPEG_VIDEO_H264_PROFILE; |
| break; |
| case VideoCodec::VP8: |
| queryId = V4L2_CID_MPEG_VIDEO_VP8_PROFILE; |
| break; |
| case VideoCodec::VP9: |
| queryId = V4L2_CID_MPEG_VIDEO_VP9_PROFILE; |
| break; |
| case VideoCodec::HEVC: |
| queryId = V4L2_CID_MPEG_VIDEO_HEVC_PROFILE; |
| break; |
| default: |
| return false; |
| } |
| |
| v4l2_queryctrl queryCtrl = {}; |
| queryCtrl.id = queryId; |
| if (ioctl(VIDIOC_QUERYCTRL, &queryCtrl) != 0) { |
| return false; |
| } |
| v4l2_querymenu queryMenu = {}; |
| queryMenu.id = queryCtrl.id; |
| for (queryMenu.index = queryCtrl.minimum; |
| static_cast<int>(queryMenu.index) <= queryCtrl.maximum; queryMenu.index++) { |
| if (ioctl(VIDIOC_QUERYMENU, &queryMenu) == 0) { |
| const C2Config::profile_t profile = |
| V4L2Device::v4L2ProfileToC2Profile(codec, queryMenu.index); |
| if (profile != C2Config::PROFILE_UNUSED) profiles->push_back(profile); |
| } |
| } |
| return true; |
| }; |
| |
| std::vector<C2Config::profile_t> profiles; |
| switch (pixFmt) { |
| case V4L2_PIX_FMT_H264: |
| case V4L2_PIX_FMT_H264_SLICE: |
| if (!getSupportedProfiles(VideoCodec::H264, &profiles)) { |
| ALOGW("Driver doesn't support QUERY H264 profiles, " |
| "use default values, Base, Main, High"); |
| profiles = { |
| C2Config::PROFILE_AVC_BASELINE, |
| C2Config::PROFILE_AVC_MAIN, |
| C2Config::PROFILE_AVC_HIGH, |
| }; |
| } |
| break; |
| case V4L2_PIX_FMT_VP8: |
| case V4L2_PIX_FMT_VP8_FRAME: |
| if (!getSupportedProfiles(VideoCodec::VP8, &profiles)) { |
| ALOGW("Driver doesn't support QUERY VP8 profiles, use default values, Profile0"); |
| profiles = {C2Config::PROFILE_VP8_0}; |
| } |
| break; |
| case V4L2_PIX_FMT_VP9: |
| case V4L2_PIX_FMT_VP9_FRAME: |
| if (!getSupportedProfiles(VideoCodec::VP9, &profiles)) { |
| ALOGW("Driver doesn't support QUERY VP9 profiles, use default values, Profile0"); |
| profiles = {C2Config::PROFILE_VP9_0}; |
| } |
| break; |
| case V4L2_PIX_FMT_HEVC: |
| case V4L2_PIX_FMT_HEVC_SLICE: |
| if (!getSupportedProfiles(VideoCodec::HEVC, &profiles)) { |
| ALOGW("Driver doesn't support QUERY HEVC profiles, " |
| "use default values, Main"); |
| profiles = { |
| C2Config::PROFILE_HEVC_MAIN, |
| }; |
| } |
| break; |
| default: |
| ALOGE("Unhandled pixelformat %s", fourccToString(pixFmt).c_str()); |
| return {}; |
| } |
| |
| // Erase duplicated profiles. |
| std::sort(profiles.begin(), profiles.end()); |
| profiles.erase(std::unique(profiles.begin(), profiles.end()), profiles.end()); |
| return profiles; |
| } |
| |
| // static |
| int32_t V4L2Device::c2ProfileToV4L2H264Profile(C2Config::profile_t profile) { |
| switch (profile) { |
| case C2Config::PROFILE_AVC_BASELINE: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; |
| case C2Config::PROFILE_AVC_MAIN: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_MAIN; |
| case C2Config::PROFILE_AVC_EXTENDED: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED; |
| case C2Config::PROFILE_AVC_HIGH: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH; |
| case C2Config::PROFILE_AVC_HIGH_10: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10; |
| case C2Config::PROFILE_AVC_HIGH_422: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422; |
| case C2Config::PROFILE_AVC_HIGH_444_PREDICTIVE: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE; |
| case C2Config::PROFILE_AVC_SCALABLE_BASELINE: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE; |
| case C2Config::PROFILE_AVC_SCALABLE_HIGH: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH; |
| case C2Config::PROFILE_AVC_STEREO_HIGH: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH; |
| case C2Config::PROFILE_AVC_MULTIVIEW_HIGH: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH; |
| default: |
| ALOGE("Add more cases as needed"); |
| return -1; |
| } |
| } |
| |
| // static |
| int32_t V4L2Device::h264LevelIdcToV4L2H264Level(uint8_t levelIdc) { |
| switch (levelIdc) { |
| case 10: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1_0; |
| case 9: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1B; |
| case 11: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1_1; |
| case 12: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1_2; |
| case 13: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1_3; |
| case 20: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_2_0; |
| case 21: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_2_1; |
| case 22: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_2_2; |
| case 30: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_3_0; |
| case 31: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_3_1; |
| case 32: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_3_2; |
| case 40: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_4_0; |
| case 41: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_4_1; |
| case 42: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_4_2; |
| case 50: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_5_0; |
| case 51: |
| return V4L2_MPEG_VIDEO_H264_LEVEL_5_1; |
| default: |
| ALOGE("Unrecognized levelIdc: %u", static_cast<uint32_t>(levelIdc)); |
| return -1; |
| } |
| } |
| |
| // static |
| v4l2_mpeg_video_bitrate_mode V4L2Device::C2BitrateModeToV4L2BitrateMode( |
| C2Config::bitrate_mode_t bitrateMode) { |
| switch (bitrateMode) { |
| case C2Config::bitrate_mode_t::BITRATE_CONST_SKIP_ALLOWED: |
| ALOGW("BITRATE_CONST_SKIP_ALLOWED not supported, defaulting to BITRATE_CONST"); |
| FALLTHROUGH; |
| case C2Config::bitrate_mode_t::BITRATE_CONST: |
| return V4L2_MPEG_VIDEO_BITRATE_MODE_CBR; |
| case C2Config::bitrate_mode_t::BITRATE_VARIABLE_SKIP_ALLOWED: |
| ALOGW("BITRATE_VARIABLE_SKIP_ALLOWED not supported, defaulting to BITRATE_VARIABLE"); |
| FALLTHROUGH; |
| case C2Config::bitrate_mode_t::BITRATE_VARIABLE: |
| return V4L2_MPEG_VIDEO_BITRATE_MODE_VBR; |
| default: |
| ALOGW("Unsupported bitrate mode %u, defaulting to BITRATE_VARIABLE", |
| static_cast<uint32_t>(bitrateMode)); |
| return V4L2_MPEG_VIDEO_BITRATE_MODE_VBR; |
| } |
| } |
| |
| // static |
| ui::Size V4L2Device::allocatedSizeFromV4L2Format(const struct v4l2_format& format) { |
| ui::Size codedSize; |
| ui::Size visibleSize; |
| VideoPixelFormat frameFormat = VideoPixelFormat::UNKNOWN; |
| size_t bytesPerLine = 0; |
| // Total bytes in the frame. |
| size_t sizeimage = 0; |
| |
| if (V4L2_TYPE_IS_MULTIPLANAR(format.type)) { |
| ALOG_ASSERT(format.fmt.pix_mp.num_planes > 0); |
| bytesPerLine = base::checked_cast<int>(format.fmt.pix_mp.plane_fmt[0].bytesperline); |
| for (size_t i = 0; i < format.fmt.pix_mp.num_planes; ++i) { |
| sizeimage += base::checked_cast<int>(format.fmt.pix_mp.plane_fmt[i].sizeimage); |
| } |
| visibleSize.set(base::checked_cast<int>(format.fmt.pix_mp.width), |
| base::checked_cast<int>(format.fmt.pix_mp.height)); |
| const uint32_t pixFmt = format.fmt.pix_mp.pixelformat; |
| const auto frameFourcc = Fourcc::fromV4L2PixFmt(pixFmt); |
| if (!frameFourcc) { |
| ALOGE("Unsupported format %s", fourccToString(pixFmt).c_str()); |
| return codedSize; |
| } |
| frameFormat = frameFourcc->toVideoPixelFormat(); |
| } else { |
| bytesPerLine = base::checked_cast<int>(format.fmt.pix.bytesperline); |
| sizeimage = base::checked_cast<int>(format.fmt.pix.sizeimage); |
| visibleSize.set(base::checked_cast<int>(format.fmt.pix.width), |
| base::checked_cast<int>(format.fmt.pix.height)); |
| const uint32_t fourcc = format.fmt.pix.pixelformat; |
| const auto frameFourcc = Fourcc::fromV4L2PixFmt(fourcc); |
| if (!frameFourcc) { |
| ALOGE("Unsupported format %s", fourccToString(fourcc).c_str()); |
| return codedSize; |
| } |
| frameFormat = frameFourcc ? frameFourcc->toVideoPixelFormat() : VideoPixelFormat::UNKNOWN; |
| } |
| |
| // V4L2 does not provide per-plane bytesperline (bpl) when different components are sharing one |
| // physical plane buffer. In this case, it only provides bpl for the first component in the |
| // plane. So we can't depend on it for calculating height, because bpl may vary within one |
| // physical plane buffer. For example, YUV420 contains 3 components in one physical plane, with |
| // Y at 8 bits per pixel, and Cb/Cr at 4 bits per pixel per component, but we only get 8 pits |
| // per pixel from bytesperline in physical plane 0. So we need to get total frame bpp from |
| // elsewhere to calculate coded height. |
| |
| // We need bits per pixel for one component only to calculate the coded width from bytesperline. |
| int planeHorizBitsPerPixel = planeHorizontalBitsPerPixel(frameFormat, 0); |
| |
| // Adding up bpp for each component will give us total bpp for all components. |
| int totalBpp = 0; |
| for (size_t i = 0; i < numPlanes(frameFormat); ++i) |
| totalBpp += planeBitsPerPixel(frameFormat, i); |
| |
| if (sizeimage == 0 || bytesPerLine == 0 || planeHorizBitsPerPixel == 0 || totalBpp == 0 || |
| (bytesPerLine * 8) % planeHorizBitsPerPixel != 0) { |
| ALOGE("Invalid format provided"); |
| return codedSize; |
| } |
| |
| // Coded width can be calculated by taking the first component's bytesperline, which in V4L2 |
| // always applies to the first component in physical plane buffer. |
| int codedWidth = bytesPerLine * 8 / planeHorizBitsPerPixel; |
| // Sizeimage is codedWidth * codedHeight * totalBpp. |
| int codedHeight = sizeimage * 8 / codedWidth / totalBpp; |
| |
| codedSize.set(codedWidth, codedHeight); |
| ALOGV("codedSize=%s", toString(codedSize).c_str()); |
| |
| // Sanity checks. Calculated coded size has to contain given visible size and fulfill buffer |
| // byte size requirements. |
| ALOG_ASSERT(contains(Rect(codedSize), Rect(visibleSize))); |
| ALOG_ASSERT(sizeimage <= allocationSize(frameFormat, codedSize)); |
| |
| return codedSize; |
| } |
| |
| // static |
| const char* V4L2Device::v4L2MemoryToString(const v4l2_memory memory) { |
| switch (memory) { |
| case V4L2_MEMORY_MMAP: |
| return "V4L2_MEMORY_MMAP"; |
| case V4L2_MEMORY_USERPTR: |
| return "V4L2_MEMORY_USERPTR"; |
| case V4L2_MEMORY_DMABUF: |
| return "V4L2_MEMORY_DMABUF"; |
| case V4L2_MEMORY_OVERLAY: |
| return "V4L2_MEMORY_OVERLAY"; |
| default: |
| return "UNKNOWN"; |
| } |
| } |
| |
| // static |
| const char* V4L2Device::v4L2BufferTypeToString(const enum v4l2_buf_type bufType) { |
| switch (bufType) { |
| 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"; |
| } |
| } |
| |
| // static |
| std::string V4L2Device::v4L2FormatToString(const struct v4l2_format& format) { |
| std::ostringstream s; |
| s << "v4l2_format type: " << format.type; |
| if (format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE || format.type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { |
| // single-planar |
| const struct v4l2_pix_format& pix = format.fmt.pix; |
| s << ", width_height: " << toString(ui::Size(pix.width, pix.height)) |
| << ", pixelformat: " << fourccToString(pix.pixelformat) << ", field: " << pix.field |
| << ", bytesperline: " << pix.bytesperline << ", sizeimage: " << pix.sizeimage; |
| } else if (V4L2_TYPE_IS_MULTIPLANAR(format.type)) { |
| const struct v4l2_pix_format_mplane& pixMp = format.fmt.pix_mp; |
| // As long as num_planes's type is uint8_t, ostringstream treats it as a char instead of an |
| // integer, which is not what we want. Casting pix_mp.num_planes unsigned int solves the |
| // issue. |
| s << ", width_height: " << toString(ui::Size(pixMp.width, pixMp.height)) |
| << ", pixelformat: " << fourccToString(pixMp.pixelformat) << ", field: " << pixMp.field |
| << ", num_planes: " << static_cast<unsigned int>(pixMp.num_planes); |
| for (size_t i = 0; i < pixMp.num_planes; ++i) { |
| const struct v4l2_plane_pix_format& plane_fmt = pixMp.plane_fmt[i]; |
| s << ", plane_fmt[" << i << "].sizeimage: " << plane_fmt.sizeimage << ", plane_fmt[" |
| << i << "].bytesperline: " << plane_fmt.bytesperline; |
| } |
| } else { |
| s << " unsupported yet."; |
| } |
| return s.str(); |
| } |
| |
| // static |
| std::string V4L2Device::v4L2BufferToString(const struct v4l2_buffer& buffer) { |
| std::ostringstream s; |
| s << "v4l2_buffer type: " << buffer.type << ", memory: " << buffer.memory |
| << ", index: " << buffer.index << " bytesused: " << buffer.bytesused |
| << ", length: " << buffer.length; |
| if (buffer.type == V4L2_BUF_TYPE_VIDEO_CAPTURE || buffer.type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { |
| // single-planar |
| if (buffer.memory == V4L2_MEMORY_MMAP) { |
| s << ", m.offset: " << buffer.m.offset; |
| } else if (buffer.memory == V4L2_MEMORY_USERPTR) { |
| s << ", m.userptr: " << buffer.m.userptr; |
| } else if (buffer.memory == V4L2_MEMORY_DMABUF) { |
| s << ", m.fd: " << buffer.m.fd; |
| }; |
| } else if (V4L2_TYPE_IS_MULTIPLANAR(buffer.type)) { |
| for (size_t i = 0; i < buffer.length; ++i) { |
| const struct v4l2_plane& plane = buffer.m.planes[i]; |
| s << ", m.planes[" << i << "](bytesused: " << plane.bytesused |
| << ", length: " << plane.length << ", data_offset: " << plane.data_offset; |
| if (buffer.memory == V4L2_MEMORY_MMAP) { |
| s << ", m.mem_offset: " << plane.m.mem_offset; |
| } else if (buffer.memory == V4L2_MEMORY_USERPTR) { |
| s << ", m.userptr: " << plane.m.userptr; |
| } else if (buffer.memory == V4L2_MEMORY_DMABUF) { |
| s << ", m.fd: " << plane.m.fd; |
| } |
| s << ")"; |
| } |
| } else { |
| s << " unsupported yet."; |
| } |
| return s.str(); |
| } |
| |
| // static |
| std::optional<VideoFrameLayout> V4L2Device::v4L2FormatToVideoFrameLayout( |
| const struct v4l2_format& format) { |
| if (!V4L2_TYPE_IS_MULTIPLANAR(format.type)) { |
| ALOGE("v4l2_buf_type is not multiplanar: 0x%" PRIx32, format.type); |
| return std::nullopt; |
| } |
| const v4l2_pix_format_mplane& pixMp = format.fmt.pix_mp; |
| const uint32_t& pixFmt = pixMp.pixelformat; |
| const auto videoFourcc = Fourcc::fromV4L2PixFmt(pixFmt); |
| if (!videoFourcc) { |
| ALOGE("Failed to convert pixel format to VideoPixelFormat: %s", |
| fourccToString(pixFmt).c_str()); |
| return std::nullopt; |
| } |
| const VideoPixelFormat videoFormat = videoFourcc->toVideoPixelFormat(); |
| const size_t numBuffers = pixMp.num_planes; |
| const size_t numColorPlanes = numPlanes(videoFormat); |
| if (numColorPlanes == 0) { |
| ALOGE("Unsupported video format for NumPlanes(): %s", |
| videoPixelFormatToString(videoFormat).c_str()); |
| return std::nullopt; |
| } |
| if (numBuffers > numColorPlanes) { |
| ALOGE("pix_mp.num_planes: %zu should not be larger than NumPlanes(%s): %zu", numBuffers, |
| videoPixelFormatToString(videoFormat).c_str(), numColorPlanes); |
| return std::nullopt; |
| } |
| // Reserve capacity in advance to prevent unnecessary vector reallocation. |
| std::vector<VideoFramePlane> planes; |
| planes.reserve(numColorPlanes); |
| for (size_t i = 0; i < numBuffers; ++i) { |
| const v4l2_plane_pix_format& planeFormat = pixMp.plane_fmt[i]; |
| planes.push_back(VideoFramePlane{planeFormat.bytesperline, 0u, planeFormat.sizeimage}); |
| } |
| // For the case that #color planes > #buffers, it fills stride of color plane which does not map |
| // to buffer. Right now only some pixel formats are supported: NV12, YUV420, YVU420. |
| if (numColorPlanes > numBuffers) { |
| const uint32_t yStride = planes[0].mStride; |
| // Note that y_stride is from v4l2 bytesperline and its type is uint32_t. It is safe to cast |
| // to size_t. |
| const size_t yStrideAbs = static_cast<size_t>(yStride); |
| switch (pixFmt) { |
| case V4L2_PIX_FMT_NV12: |
| // The stride of UV is the same as Y in NV12. The height is half of Y plane. |
| planes.push_back(VideoFramePlane{yStride, yStrideAbs * pixMp.height, |
| yStrideAbs * pixMp.height / 2}); |
| ALOG_ASSERT(2u == planes.size()); |
| break; |
| case V4L2_PIX_FMT_YUV420: |
| case V4L2_PIX_FMT_YVU420: { |
| // The spec claims that two Cx rows (including padding) is exactly as long as one Y row |
| // (including padding). So stride of Y must be even number. |
| if (yStride % 2 != 0 || pixMp.height % 2 != 0) { |
| ALOGE("Plane-Y stride and height should be even; stride: %u, height: %u", yStride, |
| pixMp.height); |
| return std::nullopt; |
| } |
| const uint32_t halfStride = yStride / 2; |
| const size_t plane0Area = yStrideAbs * pixMp.height; |
| const size_t plane1Area = plane0Area / 4; |
| planes.push_back(VideoFramePlane{halfStride, plane0Area, plane1Area}); |
| planes.push_back(VideoFramePlane{halfStride, plane0Area + plane1Area, plane1Area}); |
| ALOG_ASSERT(3u == planes.size()); |
| break; |
| } |
| default: |
| ALOGE("Cannot derive stride for each plane for pixel format %s", |
| fourccToString(pixFmt).c_str()); |
| return std::nullopt; |
| } |
| } |
| |
| return VideoFrameLayout{videoFormat, ui::Size(pixMp.width, pixMp.height), std::move(planes), |
| (numBuffers > 1)}; |
| } |
| |
| // static |
| size_t V4L2Device::getNumPlanesOfV4L2PixFmt(uint32_t pixFmt) { |
| std::optional<Fourcc> fourcc = Fourcc::fromV4L2PixFmt(pixFmt); |
| if (fourcc && fourcc->isMultiPlanar()) { |
| return numPlanes(fourcc->toVideoPixelFormat()); |
| } |
| return 1u; |
| } |
| |
| void V4L2Device::getSupportedResolution(uint32_t pixelFormat, ui::Size* minResolution, |
| ui::Size* maxResolution) { |
| maxResolution->set(0, 0); |
| minResolution->set(0, 0); |
| v4l2_frmsizeenum frameSize; |
| memset(&frameSize, 0, sizeof(frameSize)); |
| frameSize.pixel_format = pixelFormat; |
| for (; ioctl(VIDIOC_ENUM_FRAMESIZES, &frameSize) == 0; ++frameSize.index) { |
| if (frameSize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { |
| if (frameSize.discrete.width >= base::checked_cast<uint32_t>(maxResolution->width) && |
| frameSize.discrete.height >= base::checked_cast<uint32_t>(maxResolution->height)) { |
| maxResolution->set(frameSize.discrete.width, frameSize.discrete.height); |
| } |
| if (isEmpty(*minResolution) || |
| (frameSize.discrete.width <= base::checked_cast<uint32_t>(minResolution->width) && |
| frameSize.discrete.height <= |
| base::checked_cast<uint32_t>(minResolution->height))) { |
| minResolution->set(frameSize.discrete.width, frameSize.discrete.height); |
| } |
| } else if (frameSize.type == V4L2_FRMSIZE_TYPE_STEPWISE || |
| frameSize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { |
| maxResolution->set(frameSize.stepwise.max_width, frameSize.stepwise.max_height); |
| minResolution->set(frameSize.stepwise.min_width, frameSize.stepwise.min_height); |
| break; |
| } |
| } |
| if (isEmpty(*maxResolution)) { |
| maxResolution->set(1920, 1088); |
| ALOGE("GetSupportedResolution failed to get maximum resolution for fourcc %s, " |
| "fall back to %s", |
| fourccToString(pixelFormat).c_str(), toString(*maxResolution).c_str()); |
| } |
| if (isEmpty(*minResolution)) { |
| minResolution->set(16, 16); |
| ALOGE("GetSupportedResolution failed to get minimum resolution for fourcc %s, " |
| "fall back to %s", |
| fourccToString(pixelFormat).c_str(), toString(*minResolution).c_str()); |
| } |
| } |
| |
| std::vector<uint32_t> V4L2Device::enumerateSupportedPixelformats(v4l2_buf_type bufType) { |
| std::vector<uint32_t> pixelFormats; |
| |
| v4l2_fmtdesc fmtDesc; |
| memset(&fmtDesc, 0, sizeof(fmtDesc)); |
| fmtDesc.type = bufType; |
| |
| for (; ioctl(VIDIOC_ENUM_FMT, &fmtDesc) == 0; ++fmtDesc.index) { |
| ALOGV("Found %s (0x%" PRIx32 ")", fmtDesc.description, fmtDesc.pixelformat); |
| pixelFormats.push_back(fmtDesc.pixelformat); |
| } |
| |
| return pixelFormats; |
| } |
| |
| V4L2Device::SupportedDecodeProfiles V4L2Device::getSupportedDecodeProfiles( |
| const size_t numFormats, const uint32_t pixelFormats[]) { |
| SupportedDecodeProfiles supportedProfiles; |
| |
| Type type = Type::kDecoder; |
| const auto& devices = getDevicesForType(type); |
| for (const auto& device : devices) { |
| if (!openDevicePath(device.first, type)) { |
| ALOGV("Failed opening %s", device.first.c_str()); |
| continue; |
| } |
| |
| const auto& profiles = enumerateSupportedDecodeProfiles(numFormats, pixelFormats); |
| supportedProfiles.insert(supportedProfiles.end(), profiles.begin(), profiles.end()); |
| closeDevice(); |
| } |
| |
| return supportedProfiles; |
| } |
| |
| V4L2Device::SupportedEncodeProfiles V4L2Device::getSupportedEncodeProfiles() { |
| SupportedEncodeProfiles supportedProfiles; |
| |
| Type type = Type::kEncoder; |
| const auto& devices = getDevicesForType(type); |
| for (const auto& device : devices) { |
| if (!openDevicePath(device.first, type)) { |
| ALOGV("Failed opening %s", device.first.c_str()); |
| continue; |
| } |
| |
| const auto& profiles = enumerateSupportedEncodeProfiles(); |
| supportedProfiles.insert(supportedProfiles.end(), profiles.begin(), profiles.end()); |
| closeDevice(); |
| } |
| |
| return supportedProfiles; |
| } |
| |
| V4L2Device::SupportedDecodeProfiles V4L2Device::enumerateSupportedDecodeProfiles( |
| const size_t numFormats, const uint32_t pixelFormats[]) { |
| SupportedDecodeProfiles profiles; |
| |
| const auto& supportedPixelformats = |
| enumerateSupportedPixelformats(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); |
| |
| for (uint32_t pixelFormat : supportedPixelformats) { |
| if (std::find(pixelFormats, pixelFormats + numFormats, pixelFormat) == |
| pixelFormats + numFormats) |
| continue; |
| |
| SupportedDecodeProfile profile; |
| getSupportedResolution(pixelFormat, &profile.min_resolution, &profile.max_resolution); |
| |
| const auto videoCodecProfiles = v4L2PixFmtToC2Profiles(pixelFormat, false); |
| |
| for (const auto& videoCodecProfile : videoCodecProfiles) { |
| profile.profile = videoCodecProfile; |
| profiles.push_back(profile); |
| |
| ALOGV("Found decoder profile %s, resolutions: %s %s", profileToString(profile.profile), |
| toString(profile.min_resolution).c_str(), |
| toString(profile.max_resolution).c_str()); |
| } |
| } |
| |
| return profiles; |
| } |
| |
| V4L2Device::SupportedEncodeProfiles V4L2Device::enumerateSupportedEncodeProfiles() { |
| SupportedEncodeProfiles profiles; |
| |
| const auto& supportedPixelformats = |
| enumerateSupportedPixelformats(V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); |
| |
| for (const auto& pixelformat : supportedPixelformats) { |
| SupportedEncodeProfile profile; |
| profile.max_framerate_numerator = 30; |
| profile.max_framerate_denominator = 1; |
| ui::Size minResolution; |
| getSupportedResolution(pixelformat, &minResolution, &profile.max_resolution); |
| |
| const auto videoCodecProfiles = v4L2PixFmtToC2Profiles(pixelformat, true); |
| |
| for (const auto& videoCodecProfile : videoCodecProfiles) { |
| profile.profile = videoCodecProfile; |
| profiles.push_back(profile); |
| |
| ALOGV("Found encoder profile %s, max resolution: %s", profileToString(profile.profile), |
| toString(profile.max_resolution).c_str()); |
| } |
| } |
| |
| return profiles; |
| } |
| |
| bool V4L2Device::startPolling(android::V4L2DevicePoller::EventCallback eventCallback, |
| base::RepeatingClosure errorCallback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mClientSequenceChecker); |
| |
| if (!mDevicePoller) { |
| mDevicePoller = std::make_unique<android::V4L2DevicePoller>(this, "V4L2DeviceThreadPoller"); |
| } |
| |
| bool ret = mDevicePoller->startPolling(std::move(eventCallback), std::move(errorCallback)); |
| |
| if (!ret) mDevicePoller = nullptr; |
| |
| return ret; |
| } |
| |
| bool V4L2Device::stopPolling() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mClientSequenceChecker); |
| |
| return !mDevicePoller || mDevicePoller->stopPolling(); |
| } |
| |
| void V4L2Device::schedulePoll() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mClientSequenceChecker); |
| |
| if (!mDevicePoller || !mDevicePoller->isPolling()) return; |
| |
| mDevicePoller->schedulePoll(); |
| } |
| |
| bool V4L2Device::isCtrlExposed(uint32_t ctrlId) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mClientSequenceChecker); |
| |
| struct v4l2_queryctrl queryCtrl; |
| memset(&queryCtrl, 0, sizeof(queryCtrl)); |
| queryCtrl.id = ctrlId; |
| |
| return ioctl(VIDIOC_QUERYCTRL, &queryCtrl) == 0; |
| } |
| |
| bool V4L2Device::setExtCtrls(uint32_t ctrlClass, std::vector<V4L2ExtCtrl> ctrls) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mClientSequenceChecker); |
| |
| if (ctrls.empty()) return true; |
| |
| struct v4l2_ext_controls extCtrls; |
| memset(&extCtrls, 0, sizeof(extCtrls)); |
| extCtrls.ctrl_class = ctrlClass; |
| extCtrls.count = ctrls.size(); |
| extCtrls.controls = &ctrls[0].ctrl; |
| return ioctl(VIDIOC_S_EXT_CTRLS, &extCtrls) == 0; |
| } |
| |
| bool V4L2Device::isCommandSupported(uint32_t commandId) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mClientSequenceChecker); |
| |
| struct v4l2_encoder_cmd cmd; |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd = commandId; |
| |
| return ioctl(VIDIOC_TRY_ENCODER_CMD, &cmd) == 0; |
| } |
| |
| bool V4L2Device::hasCapabilities(uint32_t capabilities) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(mClientSequenceChecker); |
| |
| struct v4l2_capability caps; |
| memset(&caps, 0, sizeof(caps)); |
| if (ioctl(VIDIOC_QUERYCAP, &caps) != 0) { |
| ALOGE("Failed to query capabilities"); |
| return false; |
| } |
| |
| return (caps.capabilities & capabilities) == capabilities; |
| } |
| |
| bool V4L2Device::openDevicePath(const std::string& path, Type /*type*/) { |
| ALOG_ASSERT(!mDeviceFd.is_valid()); |
| |
| mDeviceFd.reset(HANDLE_EINTR(::open(path.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC))); |
| if (!mDeviceFd.is_valid()) return false; |
| |
| return true; |
| } |
| |
| void V4L2Device::closeDevice() { |
| ALOGV("%s()", __func__); |
| |
| mDeviceFd.reset(); |
| } |
| |
| void V4L2Device::enumerateDevicesForType(Type type) { |
| // video input/output devices are registered as /dev/videoX in V4L2. |
| static const std::string kVideoDevicePattern = "/dev/video"; |
| |
| std::string devicePattern; |
| v4l2_buf_type bufType; |
| switch (type) { |
| case Type::kDecoder: |
| devicePattern = kVideoDevicePattern; |
| bufType = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
| break; |
| case Type::kEncoder: |
| devicePattern = kVideoDevicePattern; |
| bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
| break; |
| default: |
| ALOGE("Only decoder and encoder types are supported!!"); |
| return; |
| } |
| |
| std::vector<std::string> candidatePaths; |
| |
| // TODO(posciak): Remove this legacy unnumbered device once all platforms are updated to use |
| // numbered devices. |
| candidatePaths.push_back(devicePattern); |
| |
| // We are sandboxed, so we can't query directory contents to check which devices are actually |
| // available. Try to open the first 16; if not present, we will just fail to open immediately. |
| for (int i = 0; i < 16; ++i) { |
| candidatePaths.push_back(base::StringPrintf("%s%d", devicePattern.c_str(), i)); |
| } |
| |
| Devices devices; |
| for (const auto& path : candidatePaths) { |
| if (!openDevicePath(path, type)) { |
| continue; |
| } |
| |
| const auto& supportedPixelformats = enumerateSupportedPixelformats(bufType); |
| if (!supportedPixelformats.empty()) { |
| ALOGV("Found device: %s", path.c_str()); |
| devices.push_back(std::make_pair(path, supportedPixelformats)); |
| } |
| |
| closeDevice(); |
| } |
| |
| ALOG_ASSERT(mDevicesByType.count(type) == 0u); |
| mDevicesByType[type] = devices; |
| } |
| |
| const V4L2Device::Devices& V4L2Device::getDevicesForType(Type type) { |
| if (mDevicesByType.count(type) == 0) enumerateDevicesForType(type); |
| |
| ALOG_ASSERT(mDevicesByType.count(type) != 0u); |
| return mDevicesByType[type]; |
| } |
| |
| std::string V4L2Device::getDevicePathFor(Type type, uint32_t pixFmt) { |
| const Devices& devices = getDevicesForType(type); |
| |
| for (const auto& device : devices) { |
| if (std::find(device.second.begin(), device.second.end(), pixFmt) != device.second.end()) |
| return device.first; |
| } |
| |
| return std::string(); |
| } |
| |
| } // namespace android |