// 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: 8c9190713ed9 #include "generic_v4l2_device.h" #include #include #include #include #include #include #include #include #include #include #include "base/files/scoped_file.h" #include "base/posix/eintr_wrapper.h" #include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "macros.h" namespace media { GenericV4L2Device::GenericV4L2Device() {} GenericV4L2Device::~GenericV4L2Device() { CloseDevice(); } int GenericV4L2Device::Ioctl(int request, void* arg) { DCHECK(device_fd_.is_valid()); return HANDLE_EINTR(ioctl(device_fd_.get(), request, arg)); } bool GenericV4L2Device::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* GenericV4L2Device::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 GenericV4L2Device::Munmap(void* addr, unsigned int len) { munmap(addr, len); } bool GenericV4L2Device::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 GenericV4L2Device::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 GenericV4L2Device::Initialize() { DVLOGF(3); static bool v4l2_functions_initialized = PostSandboxInitialization(); if (!v4l2_functions_initialized) { VLOGF(1) << "Failed to initialize LIBV4L2 libs"; return false; } return true; } bool GenericV4L2Device::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(type); return false; } if (!OpenDevicePath(path, type)) { 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; } std::vector GenericV4L2Device::GetDmabufsForV4L2Buffer( int index, size_t num_planes, enum v4l2_buf_type buf_type) { DVLOGF(3); DCHECK(V4L2_TYPE_IS_MULTIPLANAR(buf_type)); std::vector dmabuf_fds; for (size_t i = 0; i < num_planes; ++i) { struct v4l2_exportbuffer expbuf; memset(&expbuf, 0, sizeof(expbuf)); expbuf.type = buf_type; expbuf.index = index; expbuf.plane = i; expbuf.flags = O_CLOEXEC; if (Ioctl(VIDIOC_EXPBUF, &expbuf) != 0) { dmabuf_fds.clear(); break; } dmabuf_fds.push_back(base::ScopedFD(expbuf.fd)); } return dmabuf_fds; } std::vector GenericV4L2Device::PreferredInputFormat(Type type) { if (type == Type::kEncoder) return {V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_NV12}; return {}; } std::vector GenericV4L2Device::GetSupportedImageProcessorPixelformats( v4l2_buf_type buf_type) { std::vector supported_pixelformats; Type type = Type::kImageProcessor; const auto& devices = GetDevicesForType(type); for (const auto& device : devices) { if (!OpenDevicePath(device.first, type)) { VLOGF(1) << "Failed opening " << device.first; continue; } std::vector pixelformats = EnumerateSupportedPixelformats(buf_type); supported_pixelformats.insert(supported_pixelformats.end(), pixelformats.begin(), pixelformats.end()); CloseDevice(); } return supported_pixelformats; } VideoDecodeAccelerator::SupportedProfiles GenericV4L2Device::GetSupportedDecodeProfiles(const size_t num_formats, const 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, type)) { VLOGF(1) << "Failed opening " << device.first; continue; } const auto& profiles = EnumerateSupportedDecodeProfiles(num_formats, pixelformats); supported_profiles.insert(supported_profiles.end(), profiles.begin(), profiles.end()); CloseDevice(); } return supported_profiles; } VideoEncodeAccelerator::SupportedProfiles GenericV4L2Device::GetSupportedEncodeProfiles() { VideoEncodeAccelerator::SupportedProfiles supported_profiles; Type type = Type::kEncoder; const auto& devices = GetDevicesForType(type); for (const auto& device : devices) { if (!OpenDevicePath(device.first, type)) { 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 GenericV4L2Device::IsImageProcessingSupported() { const auto& devices = GetDevicesForType(Type::kImageProcessor); return !devices.empty(); } bool GenericV4L2Device::IsJpegDecodingSupported() { const auto& devices = GetDevicesForType(Type::kJpegDecoder); return !devices.empty(); } bool GenericV4L2Device::IsJpegEncodingSupported() { const auto& devices = GetDevicesForType(Type::kJpegEncoder); return !devices.empty(); } bool GenericV4L2Device::OpenDevicePath(const std::string& path, Type type) { DCHECK(!device_fd_.is_valid()); device_fd_.reset( HANDLE_EINTR(open(path.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC))); if (!device_fd_.is_valid()) return false; return true; } void GenericV4L2Device::CloseDevice() { DVLOGF(3); device_fd_.reset(); } // static bool GenericV4L2Device::PostSandboxInitialization() { return true; } void GenericV4L2Device::EnumerateDevicesForType(Type type) { // video input/output devices are registered as /dev/videoX in V4L2. static const std::string kVideoDevicePattern = "/dev/video"; std::string device_pattern; v4l2_buf_type buf_type; switch (type) { case Type::kDecoder: device_pattern = kVideoDevicePattern; buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; break; case Type::kEncoder: device_pattern = kVideoDevicePattern; buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; break; default: LOG(ERROR) << "Only decoder and encoder types are supported!!"; return; } std::vector candidate_paths; // TODO(posciak): Remove this legacy unnumbered device once // all platforms are updated to use numbered devices. candidate_paths.push_back(device_pattern); // 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. for (int i = 0; i < 10; ++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, type)) continue; const auto& supported_pixelformats = EnumerateSupportedPixelformats(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 GenericV4L2Device::Devices& GenericV4L2Device::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 GenericV4L2Device::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 media