| // Copyright 2014 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_device.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <libdrm/drm_fourcc.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 <unistd.h> |
| |
| #include <algorithm> |
| #include <set> |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/not_fatal_until.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "build/build_config.h" |
| #include "media/base/color_plane_layout.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/video_types.h" |
| #include "media/gpu/chromeos/fourcc.h" |
| #include "media/gpu/macros.h" |
| #include "media/gpu/v4l2/v4l2_queue.h" |
| #include "media/gpu/v4l2/v4l2_utils.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| uint32_t V4L2PixFmtToDrmFormat(uint32_t format) { |
| switch (format) { |
| case V4L2_PIX_FMT_NV12: |
| case V4L2_PIX_FMT_NV12M: |
| return DRM_FORMAT_NV12; |
| |
| case V4L2_PIX_FMT_YUV420: |
| case V4L2_PIX_FMT_YUV420M: |
| return DRM_FORMAT_YUV420; |
| |
| case V4L2_PIX_FMT_YVU420: |
| return DRM_FORMAT_YVU420; |
| |
| case V4L2_PIX_FMT_RGB32: |
| return DRM_FORMAT_ARGB8888; |
| |
| default: |
| DVLOGF(1) << "Unrecognized format " << FourccToString(format); |
| return 0; |
| } |
| } |
| |
| } // namespace |
| |
| // 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 destroy_cb) { |
| return base::MakeRefCounted<V4L2Queue>( |
| V4L2Queue::PassKey::Get(), base::BindRepeating(&V4L2Device::Ioctl, dev), |
| base::BindRepeating(&V4L2Device::SchedulePoll, dev), |
| base::BindRepeating(&V4L2Device::Mmap, dev), |
| dev->get_secure_allocate_cb(), type, std::move(destroy_cb)); |
| } |
| }; |
| |
| V4L2Device::V4L2Device() { |
| DETACH_FROM_SEQUENCE(client_sequence_checker_); |
| } |
| |
| V4L2Device::~V4L2Device() { |
| CloseDevice(); |
| } |
| |
| scoped_refptr<V4L2Queue> V4L2Device::GetQueue(enum v4l2_buf_type type) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); |
| |
| switch (type) { |
| // Supported queue types. |
| case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: |
| case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: |
| break; |
| default: |
| VLOGF(1) << "Unsupported V4L2 queue type: " << 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 = queues_.find(type); |
| if (it != queues_.end()) |
| return scoped_refptr<V4L2Queue>(it->second); |
| |
| scoped_refptr<V4L2Queue> queue = V4L2QueueFactory::CreateQueue( |
| this, type, base::BindOnce(&V4L2Device::OnQueueDestroyed, this, type)); |
| |
| queues_[type] = queue.get(); |
| return queue; |
| } |
| |
| void V4L2Device::OnQueueDestroyed(v4l2_buf_type buf_type) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); |
| |
| auto it = queues_.find(buf_type); |
| CHECK(it != queues_.end(), base::NotFatalUntil::M130); |
| queues_.erase(it); |
| } |
| |
| bool V4L2Device::Open(Type type, uint32_t v4l2_pixfmt) { |
| DVLOGF(3); |
| std::string path = GetDevicePathFor(type, v4l2_pixfmt); |
| |
| if (path.empty()) { |
| VLOGF(1) << "No devices supporting " << FourccToString(v4l2_pixfmt) |
| << " for type: " << static_cast<int>(type); |
| return false; |
| } |
| |
| if (!OpenDevicePath(path)) { |
| VLOGF(1) << "Failed opening " << path; |
| return false; |
| } |
| |
| device_poll_interrupt_fd_.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); |
| if (!device_poll_interrupt_fd_.is_valid()) { |
| VLOGF(1) << "Failed creating a poll interrupt fd"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool V4L2Device::IsValid() { |
| return device_poll_interrupt_fd_.is_valid(); |
| } |
| |
| std::string V4L2Device::GetDriverName() { |
| struct v4l2_capability caps; |
| memset(&caps, 0, sizeof(caps)); |
| if (Ioctl(VIDIOC_QUERYCAP, &caps) != 0) { |
| VPLOGF(1) << "ioctl() failed: VIDIOC_QUERYCAP" |
| << ", caps check failed: 0x" << std::hex << caps.capabilities; |
| return ""; |
| } |
| |
| return std::string(reinterpret_cast<const char*>(caps.driver)); |
| } |
| |
| // static |
| int32_t V4L2Device::VideoCodecProfileToV4L2H264Profile( |
| VideoCodecProfile profile) { |
| switch (profile) { |
| case H264PROFILE_BASELINE: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; |
| case H264PROFILE_MAIN: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_MAIN; |
| case H264PROFILE_EXTENDED: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED; |
| case H264PROFILE_HIGH: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH; |
| case H264PROFILE_HIGH10PROFILE: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10; |
| case H264PROFILE_HIGH422PROFILE: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422; |
| case H264PROFILE_HIGH444PREDICTIVEPROFILE: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE; |
| case H264PROFILE_SCALABLEBASELINE: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE; |
| case H264PROFILE_SCALABLEHIGH: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH; |
| case H264PROFILE_STEREOHIGH: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH; |
| case H264PROFILE_MULTIVIEWHIGH: |
| return V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH; |
| default: |
| DVLOGF(1) << "Add more cases as needed"; |
| return -1; |
| } |
| } |
| |
| // static |
| int32_t V4L2Device::H264LevelIdcToV4L2H264Level(uint8_t level_idc) { |
| switch (level_idc) { |
| 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: |
| DVLOGF(1) << "Unrecognized level_idc: " << static_cast<int>(level_idc); |
| return -1; |
| } |
| } |
| |
| // static |
| gfx::Size V4L2Device::AllocatedSizeFromV4L2Format( |
| const struct v4l2_format& format) { |
| gfx::Size coded_size; |
| gfx::Size visible_size; |
| VideoPixelFormat frame_format = PIXEL_FORMAT_UNKNOWN; |
| size_t bytesperline = 0; |
| // Total bytes in the frame. |
| size_t sizeimage = 0; |
| |
| if (V4L2_TYPE_IS_MULTIPLANAR(format.type)) { |
| DCHECK_GT(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); |
| } |
| visible_size.SetSize(base::checked_cast<int>(format.fmt.pix_mp.width), |
| base::checked_cast<int>(format.fmt.pix_mp.height)); |
| const uint32_t pix_fmt = format.fmt.pix_mp.pixelformat; |
| const auto frame_fourcc = Fourcc::FromV4L2PixFmt(pix_fmt); |
| if (!frame_fourcc) { |
| VLOGF(1) << "Unsupported format " << FourccToString(pix_fmt); |
| return coded_size; |
| } |
| frame_format = frame_fourcc->ToVideoPixelFormat(); |
| } else { |
| bytesperline = base::checked_cast<int>(format.fmt.pix.bytesperline); |
| sizeimage = base::checked_cast<int>(format.fmt.pix.sizeimage); |
| visible_size.SetSize(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 frame_fourcc = Fourcc::FromV4L2PixFmt(fourcc); |
| if (!frame_fourcc) { |
| VLOGF(1) << "Unsupported format " << FourccToString(fourcc); |
| return coded_size; |
| } |
| frame_format = frame_fourcc ? frame_fourcc->ToVideoPixelFormat() |
| : PIXEL_FORMAT_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 |
| // coded_width from bytesperline. |
| int plane_horiz_bits_per_pixel = |
| VideoFrame::PlaneHorizontalBitsPerPixel(frame_format, 0); |
| |
| // Adding up bpp for each component will give us total bpp for all components. |
| int total_bpp = 0; |
| for (size_t i = 0; i < VideoFrame::NumPlanes(frame_format); ++i) |
| total_bpp += VideoFrame::PlaneBitsPerPixel(frame_format, i); |
| |
| if (sizeimage == 0 || bytesperline == 0 || plane_horiz_bits_per_pixel == 0 || |
| total_bpp == 0 || (bytesperline * 8) % plane_horiz_bits_per_pixel != 0) { |
| VLOGF(1) << "Invalid format provided"; |
| return coded_size; |
| } |
| |
| // 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 coded_width = bytesperline * 8 / plane_horiz_bits_per_pixel; |
| // Sizeimage is coded_width * coded_height * total_bpp. In the case that we |
| // don't have exact alignment due to padding in the driver, round up so that |
| // the buffer is large enough. |
| std::div_t res = std::div(sizeimage * 8, coded_width * total_bpp); |
| int coded_height = res.quot + std::min(res.rem, 1); |
| |
| coded_size.SetSize(coded_width, coded_height); |
| DVLOGF(3) << "coded_size=" << coded_size.ToString(); |
| |
| // Sanity checks. Calculated coded size has to contain given visible size |
| // and fulfill buffer byte size requirements. |
| DCHECK(gfx::Rect(coded_size).Contains(gfx::Rect(visible_size))); |
| DCHECK_LE(sizeimage, VideoFrame::AllocationSize(frame_format, coded_size)); |
| |
| return coded_size; |
| } |
| |
| int V4L2Device::Ioctl(int request, void* arg) { |
| DCHECK(device_fd_.is_valid()); |
| return HANDLE_EINTR(ioctl(device_fd_.get(), request, arg)); |
| } |
| |
| bool V4L2Device::Poll(bool poll_device, bool* event_pending) { |
| struct pollfd pollfds[2]; |
| nfds_t nfds; |
| int pollfd = -1; |
| |
| pollfds[0].fd = device_poll_interrupt_fd_.get(); |
| pollfds[0].events = POLLIN | POLLERR; |
| nfds = 1; |
| |
| if (poll_device) { |
| DVLOGF(5) << "adding device fd to poll() set"; |
| pollfds[nfds].fd = device_fd_.get(); |
| pollfds[nfds].events = POLLIN | POLLOUT | POLLERR | POLLPRI; |
| pollfd = nfds; |
| nfds++; |
| } |
| |
| if (HANDLE_EINTR(poll(pollfds, nfds, -1)) == -1) { |
| VPLOGF(1) << "poll() failed"; |
| return false; |
| } |
| *event_pending = (pollfd != -1 && pollfds[pollfd].revents & POLLPRI); |
| return true; |
| } |
| |
| void* V4L2Device::Mmap(void* addr, |
| unsigned int len, |
| int prot, |
| int flags, |
| unsigned int offset) { |
| DCHECK(device_fd_.is_valid()); |
| return mmap(addr, len, prot, flags, device_fd_.get(), offset); |
| } |
| |
| void V4L2Device::Munmap(void* addr, unsigned int len) { |
| munmap(addr, len); |
| } |
| |
| bool V4L2Device::SetDevicePollInterrupt() { |
| DVLOGF(4); |
| |
| const uint64_t buf = 1; |
| if (HANDLE_EINTR(write(device_poll_interrupt_fd_.get(), &buf, sizeof(buf))) == |
| -1) { |
| VPLOGF(1) << "write() failed"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool V4L2Device::ClearDevicePollInterrupt() { |
| DVLOGF(5); |
| |
| uint64_t buf; |
| if (HANDLE_EINTR(read(device_poll_interrupt_fd_.get(), &buf, sizeof(buf))) == |
| -1) { |
| if (errno == EAGAIN) { |
| // No interrupt flag set, and we're reading nonblocking. Not an error. |
| return true; |
| } else { |
| VPLOGF(1) << "read() failed"; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool V4L2Device::CanCreateEGLImageFrom(const Fourcc fourcc) const { |
| static uint32_t kEGLImageDrmFmtsSupported[] = { |
| DRM_FORMAT_ARGB8888, |
| #if defined(ARCH_CPU_ARM_FAMILY) |
| DRM_FORMAT_NV12, |
| DRM_FORMAT_YVU420, |
| #endif |
| }; |
| |
| return base::Contains(kEGLImageDrmFmtsSupported, |
| V4L2PixFmtToDrmFormat(fourcc.ToV4L2PixFmt())); |
| } |
| |
| std::vector<uint32_t> V4L2Device::PreferredInputFormat(Type type) const { |
| if (type == Type::kEncoder) { |
| return {V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_NV12}; |
| } |
| |
| return {}; |
| } |
| |
| VideoEncodeAccelerator::SupportedRateControlMode |
| V4L2Device::GetSupportedRateControlMode() { |
| auto rate_control_mode = VideoEncodeAccelerator::kNoMode; |
| v4l2_queryctrl query_ctrl; |
| memset(&query_ctrl, 0, sizeof(query_ctrl)); |
| query_ctrl.id = V4L2_CID_MPEG_VIDEO_BITRATE_MODE; |
| if (Ioctl(VIDIOC_QUERYCTRL, &query_ctrl)) { |
| DPLOG(WARNING) << "QUERYCTRL for bitrate mode failed"; |
| return rate_control_mode; |
| } |
| |
| v4l2_querymenu query_menu; |
| memset(&query_menu, 0, sizeof(query_menu)); |
| query_menu.id = query_ctrl.id; |
| for (query_menu.index = query_ctrl.minimum; |
| base::checked_cast<int>(query_menu.index) <= query_ctrl.maximum; |
| query_menu.index++) { |
| if (Ioctl(VIDIOC_QUERYMENU, &query_menu) == 0) { |
| switch (query_menu.index) { |
| case V4L2_MPEG_VIDEO_BITRATE_MODE_CBR: |
| rate_control_mode |= VideoEncodeAccelerator::kConstantMode; |
| break; |
| case V4L2_MPEG_VIDEO_BITRATE_MODE_VBR: |
| if (!base::FeatureList::IsEnabled(kChromeOSHWVBREncoding)) { |
| DVLOGF(3) << "Skip VBR capability"; |
| break; |
| } |
| rate_control_mode |= VideoEncodeAccelerator::kVariableMode; |
| break; |
| default: |
| DVLOGF(4) << "Skip bitrate mode: " << query_menu.index; |
| break; |
| } |
| } |
| } |
| |
| return rate_control_mode; |
| } |
| |
| std::vector<uint32_t> V4L2Device::GetSupportedImageProcessorPixelformats( |
| v4l2_buf_type buf_type) { |
| std::vector<uint32_t> supported_pixelformats; |
| |
| Type type = Type::kImageProcessor; |
| const auto& devices = GetDevicesForType(type); |
| for (const auto& device : devices) { |
| if (!OpenDevicePath(device.first)) { |
| VLOGF(1) << "Failed opening " << device.first; |
| continue; |
| } |
| |
| const auto pixelformats = EnumerateSupportedPixFmts( |
| base::BindRepeating(&V4L2Device::Ioctl, this), buf_type); |
| |
| supported_pixelformats.insert(supported_pixelformats.end(), |
| pixelformats.begin(), pixelformats.end()); |
| CloseDevice(); |
| } |
| |
| return supported_pixelformats; |
| } |
| |
| VideoDecodeAccelerator::SupportedProfiles |
| V4L2Device::GetSupportedDecodeProfiles( |
| const std::vector<uint32_t>& pixelformats) { |
| VideoDecodeAccelerator::SupportedProfiles supported_profiles; |
| |
| Type type = Type::kDecoder; |
| const auto& devices = GetDevicesForType(type); |
| for (const auto& device : devices) { |
| if (!OpenDevicePath(device.first)) { |
| VLOGF(1) << "Failed opening " << device.first; |
| continue; |
| } |
| |
| const auto& profiles = EnumerateSupportedDecodeProfiles(pixelformats); |
| supported_profiles.insert(supported_profiles.end(), profiles.begin(), |
| profiles.end()); |
| CloseDevice(); |
| } |
| |
| return supported_profiles; |
| } |
| |
| VideoEncodeAccelerator::SupportedProfiles |
| V4L2Device::GetSupportedEncodeProfiles() { |
| VideoEncodeAccelerator::SupportedProfiles supported_profiles; |
| |
| Type type = Type::kEncoder; |
| const auto& devices = GetDevicesForType(type); |
| for (const auto& device : devices) { |
| if (!OpenDevicePath(device.first)) { |
| VLOGF(1) << "Failed opening " << device.first; |
| continue; |
| } |
| |
| const auto& profiles = EnumerateSupportedEncodeProfiles(); |
| supported_profiles.insert(supported_profiles.end(), profiles.begin(), |
| profiles.end()); |
| CloseDevice(); |
| } |
| |
| return supported_profiles; |
| } |
| |
| bool V4L2Device::IsImageProcessingSupported() { |
| const auto& devices = GetDevicesForType(Type::kImageProcessor); |
| return !devices.empty(); |
| } |
| |
| bool V4L2Device::IsJpegDecodingSupported() { |
| const auto& devices = GetDevicesForType(Type::kJpegDecoder); |
| return !devices.empty(); |
| } |
| |
| bool V4L2Device::IsJpegEncodingSupported() { |
| const auto& devices = GetDevicesForType(Type::kJpegEncoder); |
| return !devices.empty(); |
| } |
| |
| VideoDecodeAccelerator::SupportedProfiles |
| V4L2Device::EnumerateSupportedDecodeProfiles( |
| const std::vector<uint32_t>& pixelformats) { |
| VideoDecodeAccelerator::SupportedProfiles profiles; |
| |
| const auto v4l2_codecs_as_pix_fmts = |
| EnumerateSupportedPixFmts(base::BindRepeating(&V4L2Device::Ioctl, this), |
| V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); |
| |
| for (uint32_t pixelformat : v4l2_codecs_as_pix_fmts) { |
| if (!base::Contains(pixelformats, pixelformat)) { |
| continue; |
| } |
| |
| // Skip AV1 decoder profiles if kChromeOSHWAV1Decoder is disabled. |
| if ((pixelformat == V4L2_PIX_FMT_AV1 || |
| pixelformat == V4L2_PIX_FMT_AV1_FRAME) && |
| !base::FeatureList::IsEnabled(kChromeOSHWAV1Decoder)) { |
| continue; |
| } |
| |
| VideoDecodeAccelerator::SupportedProfile profile; |
| GetSupportedResolution(base::BindRepeating(&V4L2Device::Ioctl, this), |
| pixelformat, &profile.min_resolution, |
| &profile.max_resolution); |
| |
| const auto video_codec_profiles = EnumerateSupportedProfilesForV4L2Codec( |
| base::BindRepeating(&V4L2Device::Ioctl, this), pixelformat); |
| |
| for (const auto& video_codec_profile : video_codec_profiles) { |
| profile.profile = video_codec_profile; |
| profiles.push_back(profile); |
| |
| DVLOGF(3) << "Found decoder profile " << GetProfileName(profile.profile) |
| << ", resolutions: " << profile.min_resolution.ToString() << " " |
| << profile.max_resolution.ToString(); |
| } |
| } |
| |
| return profiles; |
| } |
| |
| VideoEncodeAccelerator::SupportedProfiles |
| V4L2Device::EnumerateSupportedEncodeProfiles() { |
| VideoEncodeAccelerator::SupportedProfiles profiles; |
| |
| const auto v4l2_codecs_as_pix_fmts = |
| EnumerateSupportedPixFmts(base::BindRepeating(&V4L2Device::Ioctl, this), |
| V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); |
| |
| for (const auto& pixelformat : v4l2_codecs_as_pix_fmts) { |
| VideoEncodeAccelerator::SupportedProfile profile; |
| profile.max_framerate_numerator = 30; |
| profile.max_framerate_denominator = 1; |
| |
| profile.rate_control_modes = GetSupportedRateControlMode(); |
| if (profile.rate_control_modes == VideoEncodeAccelerator::kNoMode) { |
| DLOG(ERROR) << "Skipped because no bitrate mode is supported for " |
| << FourccToString(pixelformat); |
| continue; |
| } |
| gfx::Size min_resolution; |
| GetSupportedResolution(base::BindRepeating(&V4L2Device::Ioctl, this), |
| pixelformat, &min_resolution, |
| &profile.max_resolution); |
| const auto video_codec_profiles = EnumerateSupportedProfilesForV4L2Codec( |
| base::BindRepeating(&V4L2Device::Ioctl, this), pixelformat); |
| |
| for (const auto& video_codec_profile : video_codec_profiles) { |
| profile.profile = video_codec_profile; |
| |
| profile.scalability_modes = GetSupportedScalabilityModesForV4L2Codec( |
| base::BindRepeating(&V4L2Device::Ioctl, this), video_codec_profile); |
| |
| profiles.push_back(profile); |
| |
| DVLOGF(3) << "Found encoder profile " << GetProfileName(profile.profile) |
| << ", max resolution: " << profile.max_resolution.ToString(); |
| } |
| } |
| |
| return profiles; |
| } |
| |
| bool V4L2Device::StartPolling(V4L2DevicePoller::EventCallback event_callback, |
| base::RepeatingClosure error_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); |
| |
| if (!device_poller_) { |
| device_poller_ = |
| std::make_unique<V4L2DevicePoller>(this, "V4L2DevicePollerThread"); |
| } |
| |
| bool ret = device_poller_->StartPolling(std::move(event_callback), |
| std::move(error_callback)); |
| |
| if (!ret) |
| device_poller_ = nullptr; |
| |
| return ret; |
| } |
| |
| bool V4L2Device::StopPolling() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); |
| |
| return !device_poller_ || device_poller_->StopPolling(); |
| } |
| |
| void V4L2Device::SchedulePoll() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); |
| |
| if (!device_poller_ || !device_poller_->IsPolling()) |
| return; |
| |
| device_poller_->SchedulePoll(); |
| } |
| |
| std::optional<struct v4l2_event> V4L2Device::DequeueEvent() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); |
| struct v4l2_event event; |
| memset(&event, 0, sizeof(event)); |
| |
| if (Ioctl(VIDIOC_DQEVENT, &event) != 0) { |
| // The ioctl will fail if there are no pending events. This is part of the |
| // normal flow, so keep this log level low. |
| VPLOGF(4) << "Failed to dequeue event"; |
| return std::nullopt; |
| } |
| |
| return event; |
| } |
| |
| V4L2RequestsQueue* V4L2Device::GetRequestsQueue() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); |
| |
| if (requests_queue_creation_called_) |
| return requests_queue_.get(); |
| |
| requests_queue_creation_called_ = true; |
| |
| struct v4l2_capability caps; |
| if (Ioctl(VIDIOC_QUERYCAP, &caps)) { |
| VPLOGF(1) << "Failed to query device capabilities."; |
| return nullptr; |
| } |
| |
| // Some devices, namely the RK3399, have multiple hardware decoder blocks. |
| // We have to find and use the matching media device, or the kernel gets |
| // confused. |
| // Note that the match persists for the lifetime of V4L2Device. In practice |
| // this should be fine, since |GetRequestsQueue()| is only called after |
| // the codec format is configured, and the VD/VDA instance is always tied |
| // to a specific format, so it will never need to switch media devices. |
| #if BUILDFLAG(IS_CHROMEOS) |
| static const std::string kRequestDevicePrefix = "/dev/media-dec"; |
| #else |
| static const std::string kRequestDevicePrefix = "/dev/media"; |
| #endif |
| |
| // We are sandboxed, so we can't query directory contents to check which |
| // devices are actually available. Try to open the first 10; if not present, |
| // we will just fail to open immediately. |
| base::ScopedFD media_fd; |
| for (int i = 0; i < 10; ++i) { |
| const auto path = kRequestDevicePrefix + base::NumberToString(i); |
| base::ScopedFD candidate_media_fd( |
| HANDLE_EINTR(open(path.c_str(), O_RDWR, 0))); |
| if (!candidate_media_fd.is_valid()) { |
| VPLOGF(2) << "Failed to open media device: " << path; |
| continue; |
| } |
| |
| struct media_device_info media_info; |
| if (HANDLE_EINTR(ioctl(candidate_media_fd.get(), MEDIA_IOC_DEVICE_INFO, |
| &media_info)) < 0) { |
| RecordMediaIoctlUMA(MediaIoctlRequests::kMediaIocDeviceInfo); |
| VPLOGF(2) << "Failed to Query media device info."; |
| continue; |
| } |
| |
| // Match the video device and the media controller by the bus_info |
| // field. This works better than the driver field if there are multiple |
| // instances of the same decoder driver in the system. However old MediaTek |
| // drivers didn't fill in the bus_info field for the media device. |
| if (strlen(reinterpret_cast<const char*>(caps.bus_info)) > 0 && |
| strlen(reinterpret_cast<const char*>(media_info.bus_info)) > 0 && |
| strncmp(reinterpret_cast<const char*>(caps.bus_info), |
| reinterpret_cast<const char*>(media_info.bus_info), |
| sizeof(caps.bus_info))) { |
| continue; |
| } |
| |
| // Fall back to matching the video device and the media controller by the |
| // driver field. The mtk-vcodec driver does not fill the card and bus fields |
| // properly, so those won't work. |
| if (strncmp(reinterpret_cast<const char*>(caps.driver), |
| reinterpret_cast<const char*>(media_info.driver), |
| sizeof(caps.driver))) { |
| continue; |
| } |
| |
| media_fd = std::move(candidate_media_fd); |
| break; |
| } |
| |
| if (!media_fd.is_valid()) { |
| VLOGF(1) << "Failed to open matching media device."; |
| return nullptr; |
| } |
| |
| // Not using std::make_unique because constructor is private. |
| std::unique_ptr<V4L2RequestsQueue> requests_queue( |
| new V4L2RequestsQueue(std::move(media_fd))); |
| requests_queue_ = std::move(requests_queue); |
| |
| return requests_queue_.get(); |
| } |
| |
| bool V4L2Device::IsCtrlExposed(uint32_t ctrl_id) { |
| struct v4l2_queryctrl query_ctrl; |
| memset(&query_ctrl, 0, sizeof(query_ctrl)); |
| query_ctrl.id = ctrl_id; |
| |
| return Ioctl(VIDIOC_QUERYCTRL, &query_ctrl) == 0; |
| } |
| |
| bool V4L2Device::SetExtCtrls(uint32_t ctrl_class, |
| std::vector<V4L2ExtCtrl> ctrls, |
| V4L2RequestRef* request_ref) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); |
| |
| if (ctrls.empty()) |
| return true; |
| |
| struct v4l2_ext_controls ext_ctrls; |
| memset(&ext_ctrls, 0, sizeof(ext_ctrls)); |
| ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL; |
| ext_ctrls.count = 0; |
| const bool use_modern_s_ext_ctrls = |
| Ioctl(VIDIOC_S_EXT_CTRLS, &ext_ctrls) == 0; |
| |
| ext_ctrls.which = |
| use_modern_s_ext_ctrls ? V4L2_CTRL_WHICH_CUR_VAL : ctrl_class; |
| ext_ctrls.count = ctrls.size(); |
| ext_ctrls.controls = &ctrls[0].ctrl; |
| |
| if (request_ref) |
| request_ref->ApplyCtrls(&ext_ctrls); |
| |
| const int result = Ioctl(VIDIOC_S_EXT_CTRLS, &ext_ctrls); |
| if (result < 0) { |
| RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocSExtCtrls); |
| if (ext_ctrls.error_idx == ext_ctrls.count) |
| VPLOGF(1) << "VIDIOC_S_EXT_CTRLS: validation failed while trying to set " |
| "controls"; |
| else |
| VPLOGF(1) << "VIDIOC_S_EXT_CTRLS: unable to set control (0x" << std::hex |
| << ctrls[ext_ctrls.error_idx].ctrl.id << ") at index (" |
| << ext_ctrls.error_idx << ") to 0x" |
| << ctrls[ext_ctrls.error_idx].ctrl.value; |
| } |
| |
| return result == 0; |
| } |
| |
| std::optional<struct v4l2_ext_control> V4L2Device::GetCtrl(uint32_t ctrl_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); |
| struct v4l2_ext_control ctrl; |
| memset(&ctrl, 0, sizeof(ctrl)); |
| struct v4l2_ext_controls ext_ctrls; |
| memset(&ext_ctrls, 0, sizeof(ext_ctrls)); |
| |
| ctrl.id = ctrl_id; |
| ext_ctrls.controls = &ctrl; |
| ext_ctrls.count = 1; |
| |
| if (Ioctl(VIDIOC_G_EXT_CTRLS, &ext_ctrls) != 0) { |
| VPLOGF(3) << "Failed to get control"; |
| return std::nullopt; |
| } |
| |
| return ctrl; |
| } |
| |
| bool V4L2Device::SetGOPLength(uint32_t gop_length) { |
| if (!SetExtCtrls(V4L2_CTRL_CLASS_MPEG, |
| {V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_GOP_SIZE, gop_length)})) { |
| // Some platforms allow setting the GOP length to 0 as |
| // a way of turning off keyframe placement. If the platform |
| // does not support turning off periodic keyframe placement, |
| // set the GOP to the maximum supported value. |
| if (gop_length == 0) { |
| v4l2_query_ext_ctrl queryctrl; |
| memset(&queryctrl, 0, sizeof(queryctrl)); |
| |
| queryctrl.id = V4L2_CTRL_CLASS_MPEG | V4L2_CID_MPEG_VIDEO_GOP_SIZE; |
| if (Ioctl(VIDIOC_QUERY_EXT_CTRL, &queryctrl) == 0) { |
| VPLOGF(3) << "Unable to set GOP to 0, instead using max : " |
| << queryctrl.maximum; |
| return SetExtCtrls( |
| V4L2_CTRL_CLASS_MPEG, |
| {V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_GOP_SIZE, queryctrl.maximum)}); |
| } |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| bool V4L2Device::OpenDevicePath(const std::string& path) { |
| DCHECK(!device_fd_.is_valid()); |
| |
| device_fd_.reset( |
| HANDLE_EINTR(open(path.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC))); |
| return device_fd_.is_valid(); |
| } |
| |
| void V4L2Device::CloseDevice() { |
| DVLOGF(3); |
| device_fd_.reset(); |
| } |
| |
| void V4L2Device::EnumerateDevicesForType(Type type) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| static const std::string kDecoderDevicePattern = "/dev/video-dec"; |
| static const std::string kEncoderDevicePattern = "/dev/video-enc"; |
| static const std::string kImageProcessorDevicePattern = "/dev/image-proc"; |
| static const std::string kJpegDecoderDevicePattern = "/dev/jpeg-dec"; |
| static const std::string kJpegEncoderDevicePattern = "/dev/jpeg-enc"; |
| #else |
| static const std::string kDecoderDevicePattern = "/dev/video"; |
| static const std::string kEncoderDevicePattern = "/dev/video"; |
| static const std::string kImageProcessorDevicePattern = "/dev/video"; |
| static const std::string kJpegDecoderDevicePattern = "/dev/video"; |
| static const std::string kJpegEncoderDevicePattern = "/dev/video"; |
| #endif |
| |
| std::string device_pattern; |
| v4l2_buf_type buf_type; |
| switch (type) { |
| case Type::kDecoder: |
| device_pattern = kDecoderDevicePattern; |
| buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
| break; |
| case Type::kEncoder: |
| device_pattern = kEncoderDevicePattern; |
| buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
| break; |
| case Type::kImageProcessor: |
| device_pattern = kImageProcessorDevicePattern; |
| buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
| break; |
| case Type::kJpegDecoder: |
| device_pattern = kJpegDecoderDevicePattern; |
| buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
| break; |
| case Type::kJpegEncoder: |
| device_pattern = kJpegEncoderDevicePattern; |
| buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
| break; |
| } |
| |
| std::vector<std::string> candidate_paths; |
| |
| // We are sandboxed, so we can't query directory contents to check which |
| // devices are actually available. Try to open the first 10; if not present, |
| // we will just fail to open immediately. |
| #if BUILDFLAG(IS_CHROMEOS) |
| constexpr int kMaxDevices = 10; |
| candidate_paths.reserve(kMaxDevices + 1); |
| |
| // TODO(posciak): Remove this legacy unnumbered device once |
| // all platforms are updated to use numbered devices. |
| candidate_paths.push_back(device_pattern); |
| #else |
| // On mainline Linux we need to check a much larger number of devices, mainly |
| // because the device pattern is shared with ISP devices. |
| constexpr int kMaxDevices = 256; |
| candidate_paths.reserve(kMaxDevices); |
| #endif |
| for (int i = 0; i < kMaxDevices; ++i) { |
| candidate_paths.push_back( |
| base::StringPrintf("%s%d", device_pattern.c_str(), i)); |
| } |
| |
| Devices devices; |
| for (const auto& path : candidate_paths) { |
| if (!OpenDevicePath(path)) { |
| continue; |
| } |
| const auto supported_pixelformats = EnumerateSupportedPixFmts( |
| base::BindRepeating(&V4L2Device::Ioctl, this), buf_type); |
| |
| if (!supported_pixelformats.empty()) { |
| DVLOGF(3) << "Found device: " << path; |
| devices.push_back(std::make_pair(path, supported_pixelformats)); |
| } |
| |
| CloseDevice(); |
| } |
| |
| DCHECK_EQ(devices_by_type_.count(type), 0u); |
| devices_by_type_[type] = devices; |
| } |
| |
| const V4L2Device::Devices& V4L2Device::GetDevicesForType(Type type) { |
| if (devices_by_type_.count(type) == 0) { |
| EnumerateDevicesForType(type); |
| } |
| |
| DCHECK_NE(devices_by_type_.count(type), 0u); |
| return devices_by_type_[type]; |
| } |
| |
| std::string V4L2Device::GetDevicePathFor(Type type, uint32_t pixfmt) { |
| const Devices& devices = GetDevicesForType(type); |
| |
| for (const auto& device : devices) { |
| if (base::Contains(device.second, pixfmt)) { |
| return device.first; |
| } |
| } |
| |
| return std::string(); |
| } |
| |
| } // namespace media |