| // 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: 09ea0d2 |
| // Note: it's also merged with generic_v4l2_device.cc (head: a9d98e6) |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <string.h> |
| #include <sys/eventfd.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| |
| #include "base/numerics/safe_conversions.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/stringprintf.h" |
| #include "v4l2_device.h" |
| |
| #define DVLOGF(level) DVLOG(level) << __func__ << "(): " |
| #define VLOGF(level) VLOG(level) << __func__ << "(): " |
| #define VPLOGF(level) VPLOG(level) << __func__ << "(): " |
| |
| namespace media { |
| |
| V4L2Device::V4L2Device() {} |
| |
| V4L2Device::~V4L2Device() { |
| CloseDevice(); |
| } |
| |
| // static |
| VideoPixelFormat V4L2Device::V4L2PixFmtToVideoPixelFormat(uint32_t pix_fmt) { |
| switch (pix_fmt) { |
| case V4L2_PIX_FMT_NV12: |
| case V4L2_PIX_FMT_NV12M: |
| return PIXEL_FORMAT_NV12; |
| |
| case V4L2_PIX_FMT_MT21: |
| return PIXEL_FORMAT_MT21; |
| |
| case V4L2_PIX_FMT_YUV420: |
| case V4L2_PIX_FMT_YUV420M: |
| return PIXEL_FORMAT_I420; |
| |
| case V4L2_PIX_FMT_YVU420: |
| return PIXEL_FORMAT_YV12; |
| |
| case V4L2_PIX_FMT_YUV422M: |
| return PIXEL_FORMAT_I422; |
| |
| case V4L2_PIX_FMT_RGB32: |
| return PIXEL_FORMAT_ARGB; |
| |
| default: |
| DVLOGF(1) << "Add more cases as needed"; |
| return PIXEL_FORMAT_UNKNOWN; |
| } |
| } |
| |
| // static |
| uint32_t V4L2Device::VideoPixelFormatToV4L2PixFmt(VideoPixelFormat format) { |
| switch (format) { |
| case PIXEL_FORMAT_NV12: |
| return V4L2_PIX_FMT_NV12M; |
| |
| case PIXEL_FORMAT_MT21: |
| return V4L2_PIX_FMT_MT21; |
| |
| case PIXEL_FORMAT_I420: |
| return V4L2_PIX_FMT_YUV420M; |
| |
| case PIXEL_FORMAT_YV12: |
| return V4L2_PIX_FMT_YVU420; |
| |
| default: |
| LOG(FATAL) << "Add more cases as needed"; |
| return 0; |
| } |
| } |
| |
| // static |
| uint32_t V4L2Device::VideoCodecProfileToV4L2PixFmt(VideoCodecProfile profile, |
| bool slice_based) { |
| if (profile >= H264PROFILE_MIN && profile <= H264PROFILE_MAX) { |
| if (slice_based) |
| return V4L2_PIX_FMT_H264_SLICE; |
| else |
| return V4L2_PIX_FMT_H264; |
| } else if (profile >= VP8PROFILE_MIN && profile <= VP8PROFILE_MAX) { |
| if (slice_based) |
| return V4L2_PIX_FMT_VP8_FRAME; |
| else |
| return V4L2_PIX_FMT_VP8; |
| } else if (profile >= VP9PROFILE_MIN && profile <= VP9PROFILE_MAX) { |
| if (slice_based) |
| return V4L2_PIX_FMT_VP9_FRAME; |
| else |
| return V4L2_PIX_FMT_VP9; |
| } else { |
| LOG(FATAL) << "Add more cases as needed"; |
| return 0; |
| } |
| } |
| |
| // static |
| std::vector<VideoCodecProfile> V4L2Device::V4L2PixFmtToVideoCodecProfiles( |
| uint32_t pix_fmt, |
| bool is_encoder) { |
| VideoCodecProfile min_profile, max_profile; |
| std::vector<VideoCodecProfile> profiles; |
| |
| switch (pix_fmt) { |
| case V4L2_PIX_FMT_H264: |
| case V4L2_PIX_FMT_H264_SLICE: |
| if (is_encoder) { |
| // TODO(posciak): need to query the device for supported H.264 profiles, |
| // for now choose Main as a sensible default. |
| min_profile = H264PROFILE_MAIN; |
| max_profile = H264PROFILE_MAIN; |
| } else { |
| min_profile = H264PROFILE_MIN; |
| max_profile = H264PROFILE_MAX; |
| } |
| break; |
| |
| case V4L2_PIX_FMT_VP8: |
| case V4L2_PIX_FMT_VP8_FRAME: |
| min_profile = VP8PROFILE_MIN; |
| max_profile = VP8PROFILE_MAX; |
| break; |
| |
| case V4L2_PIX_FMT_VP9: |
| case V4L2_PIX_FMT_VP9_FRAME: |
| min_profile = VP9PROFILE_MIN; |
| max_profile = VP9PROFILE_MAX; |
| break; |
| |
| default: |
| VLOGF(1) << "Unhandled pixelformat " << std::hex << "0x" << pix_fmt; |
| return profiles; |
| } |
| |
| for (int profile = min_profile; profile <= max_profile; ++profile) |
| profiles.push_back(static_cast<VideoCodecProfile>(profile)); |
| |
| return profiles; |
| } |
| |
| 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) << "Poll(): 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::Open(Type type, uint32_t v4l2_pixfmt) { |
| VLOGF(2); |
| std::string path = GetDevicePathFor(type, v4l2_pixfmt); |
| |
| if (path.empty()) { |
| VLOGF(1) << "No devices supporting " << std::hex << "0x" << v4l2_pixfmt |
| << " for type: " << static_cast<int>(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<base::ScopedFD> V4L2Device::GetDmabufsForV4L2Buffer( |
| int index, |
| size_t num_planes, |
| enum v4l2_buf_type buf_type) { |
| VLOGF(2); |
| DCHECK(V4L2_TYPE_IS_MULTIPLANAR(buf_type)); |
| |
| std::vector<base::ScopedFD> dmabuf_fds; |
| for (size_t i = 0; i < num_planes; ++i) { |
| struct v4l2_exportbuffer expbuf; |
| memset(&expbuf, 0, sizeof(expbuf)); |
| expbuf.type = buf_type; |
| expbuf.index = index; |
| expbuf.plane = i; |
| expbuf.flags = O_CLOEXEC; |
| if (Ioctl(VIDIOC_EXPBUF, &expbuf) != 0) { |
| dmabuf_fds.clear(); |
| break; |
| } |
| |
| dmabuf_fds.push_back(base::ScopedFD(expbuf.fd)); |
| } |
| |
| return dmabuf_fds; |
| } |
| |
| VideoDecodeAccelerator::SupportedProfiles |
| V4L2Device::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; |
| } |
| |
| void V4L2Device::GetSupportedResolution(uint32_t pixelformat, |
| Size* min_resolution, |
| Size* max_resolution) { |
| max_resolution->SetSize(0, 0); |
| min_resolution->SetSize(0, 0); |
| v4l2_frmsizeenum frame_size; |
| memset(&frame_size, 0, sizeof(frame_size)); |
| frame_size.pixel_format = pixelformat; |
| for (; Ioctl(VIDIOC_ENUM_FRAMESIZES, &frame_size) == 0; ++frame_size.index) { |
| if (frame_size.type == V4L2_FRMSIZE_TYPE_DISCRETE) { |
| if (frame_size.discrete.width >= |
| base::checked_cast<uint32_t>(max_resolution->width()) && |
| frame_size.discrete.height >= |
| base::checked_cast<uint32_t>(max_resolution->height())) { |
| max_resolution->SetSize(frame_size.discrete.width, |
| frame_size.discrete.height); |
| } |
| if (min_resolution->IsEmpty() || |
| (frame_size.discrete.width <= |
| base::checked_cast<uint32_t>(min_resolution->width()) && |
| frame_size.discrete.height <= |
| base::checked_cast<uint32_t>(min_resolution->height()))) { |
| min_resolution->SetSize(frame_size.discrete.width, |
| frame_size.discrete.height); |
| } |
| } else if (frame_size.type == V4L2_FRMSIZE_TYPE_STEPWISE || |
| frame_size.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { |
| max_resolution->SetSize(frame_size.stepwise.max_width, |
| frame_size.stepwise.max_height); |
| min_resolution->SetSize(frame_size.stepwise.min_width, |
| frame_size.stepwise.min_height); |
| break; |
| } |
| } |
| if (max_resolution->IsEmpty()) { |
| max_resolution->SetSize(1920, 1088); |
| VLOGF(1) << "GetSupportedResolution failed to get maximum resolution for " |
| << "fourcc " << std::hex << pixelformat |
| << ", fall back to " << max_resolution->ToString(); |
| } |
| if (min_resolution->IsEmpty()) { |
| min_resolution->SetSize(16, 16); |
| VLOGF(1) << "GetSupportedResolution failed to get minimum resolution for " |
| << "fourcc " << std::hex << pixelformat |
| << ", fall back to " << min_resolution->ToString(); |
| } |
| } |
| |
| std::vector<uint32_t> V4L2Device::EnumerateSupportedPixelformats( |
| v4l2_buf_type buf_type) { |
| std::vector<uint32_t> pixelformats; |
| |
| v4l2_fmtdesc fmtdesc; |
| memset(&fmtdesc, 0, sizeof(fmtdesc)); |
| fmtdesc.type = buf_type; |
| |
| for (; Ioctl(VIDIOC_ENUM_FMT, &fmtdesc) == 0; ++fmtdesc.index) { |
| DVLOGF(3) << "Found " << fmtdesc.description << std::hex << " (0x" |
| << fmtdesc.pixelformat << ")"; |
| pixelformats.push_back(fmtdesc.pixelformat); |
| } |
| |
| return pixelformats; |
| } |
| |
| VideoDecodeAccelerator::SupportedProfiles |
| V4L2Device::EnumerateSupportedDecodeProfiles(const size_t num_formats, |
| const uint32_t pixelformats[]) { |
| VideoDecodeAccelerator::SupportedProfiles profiles; |
| |
| const auto& supported_pixelformats = |
| EnumerateSupportedPixelformats(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); |
| |
| for (uint32_t pixelformat : supported_pixelformats) { |
| if (std::find(pixelformats, pixelformats + num_formats, pixelformat) == |
| pixelformats + num_formats) |
| continue; |
| |
| VideoDecodeAccelerator::SupportedProfile profile; |
| GetSupportedResolution(pixelformat, &profile.min_resolution, |
| &profile.max_resolution); |
| |
| const auto video_codec_profiles = |
| V4L2PixFmtToVideoCodecProfiles(pixelformat, false); |
| |
| 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; |
| } |
| |
| bool V4L2Device::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 V4L2Device::CloseDevice() { |
| VLOGF(2); |
| device_fd_.reset(); |
| } |
| |
| void V4L2Device::EnumerateDevicesForType(Type type) { |
| static const std::string kDecoderDevicePattern = "/dev/video-dec"; |
| 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; |
| default: |
| LOG(ERROR) << "Only decoder type is supported!!"; |
| return; |
| } |
| |
| std::vector<std::string> 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 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 (std::find(device.second.begin(), device.second.end(), pixfmt) != |
| device.second.end()) |
| return device.first; |
| } |
| |
| return std::string(); |
| } |
| |
| } // namespace media |