// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/v4l2/test/v4l2_ioctl_shim.h"
#include <fcntl.h>
#include <linux/media.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <string_view>
#include <unordered_map>
#include "base/containers/contains.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/video_types.h"
#include "media/gpu/macros.h"
namespace media {
namespace v4l2_test {
constexpr int kIoctlOk = 0;
#define V4L2_REQUEST_CODE_AND_STRING(x) \
{ x, #x }
constexpr uint32_t kMaximumDeviceNumber = 150;
constexpr char kDecoderDevicePrefix[] = "/dev/video";
constexpr char kMediaDevicePrefix[] = "/dev/media";
// This map maintains a table with pairs of V4L2 request code
// and corresponding name. New pair has to be added here
// when new V4L2 request code has to be used.
static const std::unordered_map<int, std::string>
kMapFromV4L2RequestCodeToString = {
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_QUERYCAP),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_QUERYCTRL),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_ENUM_FMT),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_ENUM_FRAMESIZES),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_S_FMT),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_G_FMT),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_TRY_FMT),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_REQBUFS),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_QUERYBUF),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_QBUF),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_DQBUF),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_STREAMON),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_STREAMOFF),
V4L2_REQUEST_CODE_AND_STRING(VIDIOC_S_EXT_CTRLS),
V4L2_REQUEST_CODE_AND_STRING(MEDIA_IOC_DEVICE_INFO),
V4L2_REQUEST_CODE_AND_STRING(MEDIA_IOC_REQUEST_ALLOC),
V4L2_REQUEST_CODE_AND_STRING(MEDIA_REQUEST_IOC_QUEUE),
V4L2_REQUEST_CODE_AND_STRING(MEDIA_REQUEST_IOC_REINIT)};
// Finds corresponding defined V4L2 request code name
// for a given V4L2 request code value.
std::string V4L2RequestCodeToString(int request_code) {
DCHECK(base::Contains(kMapFromV4L2RequestCodeToString, request_code));
const auto& request_code_pair =
kMapFromV4L2RequestCodeToString.find(request_code);
return request_code_pair->second;
}
// Returns buffer type name (OUTPUT or CAPTURE) given v4l2_buf_type |type|.
std::ostream& operator<<(std::ostream& ostream,
const enum v4l2_buf_type& type) {
ostream << ((type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ? "OUTPUT"
: "CAPTURE");
return ostream;
}
// Returns resolution and number of planes given |pix_mp|.
std::ostream& operator<<(std::ostream& ostream,
const struct v4l2_pix_format_mplane& pix_mp) {
ostream << media::FourccToString(pix_mp.pixelformat) << ", " << pix_mp.width
<< " x " << pix_mp.height
<< ", num_planes = " << static_cast<size_t>(pix_mp.num_planes) << ".";
return ostream;
}
// Logs whether given ioctl request |request_code| succeeded
// or failed given |ret|.
void LogIoctlResult(int ret, int request_code) {
if (ret != kIoctlOk) {
switch (errno) {
case EAGAIN:
LOG(INFO) << "Ioctl request failed for "
<< V4L2RequestCodeToString(request_code)
<< " with error code EAGAIN.";
break;
case EBUSY:
LOG(WARNING) << "Ioctl request returned EBUSY for "
<< V4L2RequestCodeToString(request_code)
<< " and should be retried.";
break;
default:
LOG(ERROR) << "Ioctl request failed for "
<< V4L2RequestCodeToString(request_code) << ".";
}
}
VLOG_IF(4, ret == kIoctlOk)
<< V4L2RequestCodeToString(request_code) << " succeeded.";
}
// Enumeration Ioctls are expected to return an error at the end of the list.
// Don't error on this message because this is the way that the client knows
// there are no more values to enumerate.
void LogIoctlResultForEnum(int ret, int request_code) {
if (ret != kIoctlOk) {
VLOG(1) << V4L2RequestCodeToString(request_code) << " failed(" << ret
<< ").";
} else {
VLOG(4) << V4L2RequestCodeToString(request_code) << " succeeded.";
}
}
MmappedBuffer::MmappedBuffer(const base::PlatformFile ioctl_fd,
const struct v4l2_buffer& v4l2_buffer)
: num_planes_(v4l2_buffer.length), buffer_id_(0) {
for (uint32_t i = 0; i < num_planes_; ++i) {
void* start_addr =
mmap(NULL, v4l2_buffer.m.planes[i].length, PROT_READ | PROT_WRITE,
MAP_SHARED, ioctl_fd, v4l2_buffer.m.planes[i].m.mem_offset);
LOG_IF(FATAL, start_addr == MAP_FAILED)
<< "Failed to mmap buffer of length(" << v4l2_buffer.m.planes[i].length
<< ") and offset(" << std::hex << v4l2_buffer.m.planes[i].m.mem_offset
<< ").";
mmapped_planes_.emplace_back(start_addr, v4l2_buffer.m.planes[i].length);
}
}
MmappedBuffer::~MmappedBuffer() {
for (const auto& [start_addr, length, bytes_used] : mmapped_planes_) {
munmap(start_addr, length);
}
}
V4L2Queue::V4L2Queue(enum v4l2_buf_type type,
const gfx::Size& resolution,
enum v4l2_memory memory)
: type_(type),
num_buffers_(0),
resolution_(resolution),
num_planes_(1),
memory_(memory) {}
V4L2Queue::~V4L2Queue() = default;
scoped_refptr<MmappedBuffer> V4L2Queue::GetBuffer(const size_t index) const {
DCHECK_LT(index, buffers_.size());
return buffers_[index];
}
template <typename T>
bool V4L2IoctlShim::Ioctl(int request_code, T arg) const {
NOTREACHED_IN_MIGRATION()
<< "Please add a specialized function for the given V4L2 ioctl "
"request code.";
return !kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code, struct v4l2_capability* cap) const {
DCHECK_EQ(request_code, static_cast<int>(VIDIOC_QUERYCAP));
LOG_ASSERT(cap != nullptr) << "|cap| check failed.";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, cap);
LogIoctlResult(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code,
struct v4l2_queryctrl* query_ctrl) const {
DCHECK_EQ(request_code, static_cast<int>(VIDIOC_QUERYCTRL));
LOG_ASSERT(query_ctrl != nullptr) << "|query_ctrl| check failed.";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, query_ctrl);
LogIoctlResultForEnum(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code,
struct v4l2_fmtdesc* fmtdesc) const {
DCHECK_EQ(request_code, static_cast<int>(VIDIOC_ENUM_FMT));
LOG_ASSERT(fmtdesc != nullptr) << "|fmtdesc| check failed.";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, fmtdesc);
LogIoctlResultForEnum(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code,
struct v4l2_frmsizeenum* frame_size) const {
DCHECK_EQ(request_code, static_cast<int>(VIDIOC_ENUM_FRAMESIZES));
LOG_ASSERT(frame_size != nullptr) << "|frame_size| check failed.";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, frame_size);
LogIoctlResultForEnum(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code, struct v4l2_format* fmt) const {
DCHECK(request_code == static_cast<int>(VIDIOC_S_FMT) ||
request_code == static_cast<int>(VIDIOC_G_FMT) ||
request_code == static_cast<int>(VIDIOC_TRY_FMT));
LOG_ASSERT(fmt != nullptr) << "|fmt| check failed.";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, fmt);
LogIoctlResult(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code,
struct v4l2_requestbuffers* reqbuf) const {
DCHECK_EQ(request_code, static_cast<int>(VIDIOC_REQBUFS));
LOG_ASSERT(reqbuf != nullptr) << "|reqbuf| check failed.";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, reqbuf);
LogIoctlResult(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code, struct v4l2_buffer* buffer) const {
DCHECK(request_code == static_cast<int>(VIDIOC_QUERYBUF) ||
request_code == static_cast<int>(VIDIOC_QBUF) ||
request_code == static_cast<int>(VIDIOC_DQBUF));
LOG_ASSERT(buffer != nullptr) << "|buffer| check failed.";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, buffer);
LogIoctlResult(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code, int* arg) const {
DCHECK(request_code == static_cast<int>(VIDIOC_STREAMON) ||
request_code == static_cast<int>(VIDIOC_STREAMOFF) ||
request_code == static_cast<int>(MEDIA_IOC_REQUEST_ALLOC));
LOG_ASSERT(arg != nullptr) << "|arg| check failed.";
base::PlatformFile ioctl_fd;
if (request_code == static_cast<int>(MEDIA_IOC_REQUEST_ALLOC)) {
ioctl_fd = media_fd_.GetPlatformFile();
} else {
ioctl_fd = decode_fd_.GetPlatformFile();
}
const int ret = ioctl(ioctl_fd, request_code, arg);
LogIoctlResult(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code,
struct media_device_info* info) const {
DCHECK_EQ(request_code, static_cast<int>(MEDIA_IOC_DEVICE_INFO));
LOG_ASSERT(info != nullptr) << "|media_device_info| check failed.";
const int ret = ioctl(media_fd_.GetPlatformFile(), request_code, info);
LogIoctlResult(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code, int arg) const {
DCHECK(request_code == static_cast<int>(MEDIA_REQUEST_IOC_QUEUE) ||
request_code == static_cast<int>(MEDIA_REQUEST_IOC_REINIT));
const int ret = ioctl(arg, request_code);
LogIoctlResult(ret, request_code);
return ret == kIoctlOk;
}
template <>
bool V4L2IoctlShim::Ioctl(int request_code,
struct v4l2_ext_controls* ctrls) const {
DCHECK(request_code == static_cast<int>(VIDIOC_S_EXT_CTRLS));
LOG_ASSERT(ctrls != nullptr) << "|ctrls| check failed.";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, ctrls);
LogIoctlResult(ret, request_code);
return ret == kIoctlOk;
}
V4L2IoctlShim::V4L2IoctlShim(const uint32_t coded_fourcc) {
// TODO(b/278748005): Remove |cur_val_is_supported_| when all drivers
// fully support |V4L2_CTRL_WHICH_CUR_VAL|
// On kernel version 5.4 the MTK driver for MT8192 does not correctly support
// |V4L2_CTRL_WHICH_CUR_VAL|. This parameter is used when calling
// VIDIOC_S_EXT_CTRLS to indicate that the call should be executed
// immediately instead of putting it in a queue. Making sure the first
// buffer is processed immediately is only necessary for codecs that
// support 10 bit profiles. When processing a 10 bit profile the parameters
// need to be processed before the format can be determined. There are no
// chipsets that are on kernels older 5.10 and produce 10 bit output.
constexpr char kKernelVersion5dot4[] = "Linux version 5.4*";
std::string kernel_version;
ReadFileToString(base::FilePath("/proc/version"), &kernel_version);
cur_val_is_supported_ =
!base::MatchPattern(kernel_version, kKernelVersion5dot4);
for (uint32_t i = 0; i < kMaximumDeviceNumber; ++i) {
std::string path =
std::string(kDecoderDevicePrefix) + base::NumberToString(i);
decode_fd_ = base::File(base::FilePath(path), base::File::FLAG_OPEN |
base::File::FLAG_READ |
base::File::FLAG_WRITE);
// Check if the device supports the requested coded format
if (QueryFormat(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, coded_fourcc)) {
break;
}
// Close file descriptor on failure
decode_fd_.Close();
}
PCHECK(decode_fd_.IsValid()) << "Failed to find available decode device.";
struct v4l2_capability cap;
memset(&cap, 0, sizeof(cap));
const bool ret = Ioctl(VIDIOC_QUERYCAP, &cap);
DCHECK(ret);
LOG(INFO) << "Driver=\"" << cap.driver << "\" bus_info=\"" << cap.bus_info
<< "\" card=\"" << cap.card;
if (!(cap.capabilities & V4L2_CAP_VIDEO_M2M_MPLANE)) {
LOG(FATAL)
<< "Multi planar format required, but not supported by the driver.";
}
if (!FindMediaDevice(&cap)) {
LOG(FATAL) << "Failed to find available media device.";
}
PCHECK(media_fd_.IsValid()) << "Media device fd is not valid.";
}
V4L2IoctlShim::~V4L2IoctlShim() = default;
bool V4L2IoctlShim::QueryCtrl(const uint32_t ctrl_id) const {
struct v4l2_queryctrl query_ctrl;
memset(&query_ctrl, 0, sizeof(query_ctrl));
query_ctrl.id = ctrl_id;
return Ioctl(VIDIOC_QUERYCTRL, &query_ctrl);
}
bool V4L2IoctlShim::EnumFrameSizes(uint32_t fourcc) const {
struct v4l2_frmsizeenum frame_size;
memset(&frame_size, 0, sizeof(frame_size));
frame_size.pixel_format = fourcc;
return Ioctl(VIDIOC_ENUM_FRAMESIZES, &frame_size);
}
void V4L2IoctlShim::SetFmt(const std::unique_ptr<V4L2Queue>& queue) const {
struct v4l2_format fmt;
if (queue->type() == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
// TODO(stevecho): remove VIDIOC_ENUM_FRAMESIZES ioctl call
// after b/193237015 is resolved.
if (!EnumFrameSizes(queue->fourcc())) {
LOG(INFO) << "EnumFrameSizes for OUTPUT queue failed.";
}
}
memset(&fmt, 0, sizeof(fmt));
fmt.type = queue->type();
fmt.fmt.pix_mp.pixelformat = queue->fourcc();
if (queue->type() == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
constexpr size_t kInputBufferMaxSize = 4 * 1024 * 1024;
fmt.fmt.pix_mp.plane_fmt[0].sizeimage = kInputBufferMaxSize;
}
fmt.fmt.pix_mp.num_planes = queue->num_planes();
fmt.fmt.pix_mp.width = queue->resolution().width();
fmt.fmt.pix_mp.height = queue->resolution().height();
const bool ret = Ioctl(VIDIOC_S_FMT, &fmt);
LOGF(INFO) << queue->type() << " - VIDIOC_S_FMT: " << fmt.fmt.pix_mp;
LOG_ASSERT(ret) << "VIDIOC_S_FMT for " << queue->type() << " queue failed.";
}
void V4L2IoctlShim::GetFmt(struct v4l2_format* fmt) const {
const bool ret = Ioctl(VIDIOC_G_FMT, fmt);
const enum v4l2_buf_type type = static_cast<enum v4l2_buf_type>(fmt->type);
LOGF(INFO) << type << " - VIDIOC_G_FMT: " << fmt->fmt.pix_mp;
LOG_ASSERT(ret) << "VIDIOC_G_FMT for " << type << " queue failed.";
}
void V4L2IoctlShim::TryFmt(struct v4l2_format* fmt) const {
const bool ret = Ioctl(VIDIOC_TRY_FMT, fmt);
const enum v4l2_buf_type type = static_cast<enum v4l2_buf_type>(fmt->type);
LOGF(INFO) << type << " - VIDIOC_TRY_FMT: " << fmt->fmt.pix_mp;
LOG_ASSERT(ret) << "VIDIOC_TRY_FMT for " << type << " queue failed.";
}
void V4L2IoctlShim::ReqBufs(std::unique_ptr<V4L2Queue>& queue,
uint32_t count) const {
struct v4l2_requestbuffers reqbuf;
memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.count = count;
reqbuf.type = queue->type();
reqbuf.memory = queue->memory();
const bool ret = Ioctl(VIDIOC_REQBUFS, &reqbuf);
queue->set_num_buffers(reqbuf.count);
if (count == 0) {
LOGF(INFO) << "Requested to free all buffers in " << queue->type()
<< " with a buffer count of 0.";
} else {
LOGF(INFO) << queue->num_buffers() << " buffers requested, " << reqbuf.count
<< " buffers returned for " << queue->type() << ".";
}
LOG_ASSERT(ret) << "VIDIOC_REQBUFS for " << queue->type() << " queue failed.";
}
bool V4L2IoctlShim::QBuf(const std::unique_ptr<V4L2Queue>& queue,
const uint32_t buffer_id) const {
LOG_ASSERT(queue->memory() == V4L2_MEMORY_MMAP)
<< "Only V4L2_MEMORY_MMAP is currently supported.";
struct v4l2_buffer v4l2_buffer;
std::vector<v4l2_plane> planes(VIDEO_MAX_PLANES);
memset(&v4l2_buffer, 0, sizeof v4l2_buffer);
v4l2_buffer.type = queue->type();
v4l2_buffer.memory = queue->memory();
v4l2_buffer.index = buffer_id;
v4l2_buffer.m.planes = planes.data();
v4l2_buffer.length = queue->num_planes();
scoped_refptr<MmappedBuffer> buffer = queue->GetBuffer(buffer_id);
for (uint32_t i = 0; i < queue->num_planes(); ++i) {
v4l2_buffer.m.planes[i].length = buffer->mmapped_planes()[i].length;
v4l2_buffer.m.planes[i].bytesused = buffer->mmapped_planes()[i].bytes_used;
v4l2_buffer.m.planes[i].data_offset = 0;
}
// Request API related setting is needed only for OUTPUT queue.
if (queue->type() == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
v4l2_buffer.flags |= V4L2_BUF_FLAG_REQUEST_FD;
v4l2_buffer.request_fd = queue->media_request_fd();
v4l2_buffer.timestamp.tv_usec =
base::checked_cast<__suseconds_t>(buffer->frame_number());
}
return Ioctl(VIDIOC_QBUF, &v4l2_buffer);
}
void V4L2IoctlShim::DQBuf(const std::unique_ptr<V4L2Queue>& queue,
uint32_t* buffer_id) const {
LOG_ASSERT(queue->memory() == V4L2_MEMORY_MMAP)
<< "Only V4L2_MEMORY_MMAP is currently supported.";
LOG_ASSERT(buffer_id != nullptr) << "|buffer_id| check failed.";
struct v4l2_buffer v4l2_buffer;
std::vector<v4l2_plane> planes(VIDEO_MAX_PLANES);
memset(&v4l2_buffer, 0, sizeof v4l2_buffer);
v4l2_buffer.type = queue->type();
v4l2_buffer.memory = queue->memory();
v4l2_buffer.m.planes = planes.data();
v4l2_buffer.length = queue->num_planes();
const bool ret = Ioctl(VIDIOC_DQBUF, &v4l2_buffer);
LOG_ASSERT(ret) << "VIDIOC_DQBUF failed for " << queue->type() << " queue.";
// V4L2 explains |index| to be id number of the buffer. We are using
// |buffer_id| (or |id|) instead of |index| consistently in the platform
// decoding code to avoid confusion.
const uint32_t id = v4l2_buffer.index;
if (queue->type() == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
// We set |v4l2_buffer.timestamp.tv_usec| in the encoded chunk enqueued in
// the OUTPUT queue, and the driver propagates it to the corresponding
// decoded video frame (or at least is expected to). This gives us
// information about which encoded frame corresponds to the current
// decoded video frame.
queue->GetBuffer(id)->set_buffer_id(id);
queue->GetBuffer(id)->set_frame_number(v4l2_buffer.timestamp.tv_usec);
}
*buffer_id = id;
}
void V4L2IoctlShim::StreamOn(const enum v4l2_buf_type type) const {
int arg = static_cast<int>(type);
const bool ret = Ioctl(VIDIOC_STREAMON, &arg);
LOG_ASSERT(ret) << "VIDIOC_STREAMON for " << type << " queue failed.";
}
void V4L2IoctlShim::StreamOff(const enum v4l2_buf_type type) const {
int arg = static_cast<int>(type);
const bool ret = Ioctl(VIDIOC_STREAMOFF, &arg);
LOG_ASSERT(ret) << "VIDIOC_STREAMOFF for " << type << " queue failed.";
}
void V4L2IoctlShim::SetExtCtrls(const std::unique_ptr<V4L2Queue>& queue,
v4l2_ext_controls* ext_ctrls,
bool immediate) const {
// TODO(b/230021497): add compressed header probability related change
// when V4L2_CID_STATELESS_VP9_COMPRESSED_HDR is supported
// "If |request_fd| is set to a not-yet-queued request file descriptor
// and |which| is set to V4L2_CTRL_WHICH_REQUEST_VAL, then the controls
// are not applied immediately when calling VIDIOC_S_EXT_CTRLS, but
// instead are applied by the driver for the buffer associated with
// the same request.", see:
// https://www.kernel.org/doc/html/v5.10/userspace-api/media/v4l/vidioc-g-ext-ctrls.html#description
// Unmentioned in that documentation is that |V4L2_CTRL_WHICH_CUR_VAL| will
// force the request to be processed immediately instead of being queue.
if (immediate) {
ext_ctrls->which = cur_val_is_supported_ ? V4L2_CTRL_WHICH_CUR_VAL
: V4L2_CTRL_WHICH_REQUEST_VAL;
} else {
ext_ctrls->which = V4L2_CTRL_WHICH_REQUEST_VAL;
}
ext_ctrls->request_fd = queue->media_request_fd();
const bool ret = Ioctl(VIDIOC_S_EXT_CTRLS, ext_ctrls);
LOG_ASSERT(ret) << "VIDIOC_S_EXT_CTRLS failed.";
}
void V4L2IoctlShim::MediaIocRequestAlloc(int* media_request_fd) const {
LOG_ASSERT(media_request_fd != nullptr)
<< "|media_request_fd| check failed.\n";
int allocated_req_fd;
const bool ret = Ioctl(MEDIA_IOC_REQUEST_ALLOC, &allocated_req_fd);
if (ret)
*media_request_fd = allocated_req_fd;
LOG_ASSERT(ret) << "MEDIA_IOC_REQUEST_ALLOC failed";
}
void V4L2IoctlShim::MediaRequestIocQueue(
const std::unique_ptr<V4L2Queue>& queue) const {
int req_fd = queue->media_request_fd();
const bool ret = Ioctl(MEDIA_REQUEST_IOC_QUEUE, req_fd);
LOG_ASSERT(ret) << "MEDIA_REQUEST_IOC_QUEUE failed.";
}
void V4L2IoctlShim::MediaRequestIocReinit(
const std::unique_ptr<V4L2Queue>& queue) const {
int req_fd = queue->media_request_fd();
const bool ret = Ioctl(MEDIA_REQUEST_IOC_REINIT, req_fd);
LOG_ASSERT(ret) << "MEDIA_REQUEST_IOC_REINIT failed.";
}
void V4L2IoctlShim::WaitForRequestCompletion(
const std::unique_ptr<V4L2Queue>& queue) const {
struct pollfd pollfds[] = {
{.fd = queue->media_request_fd(), .events = POLLPRI}};
// There are some test vectors that are not expected to play back at real
// time. 250ms corresponds to 4fps.
constexpr int kPollTimeoutMS = 250;
const int poll_result =
HANDLE_EINTR(poll(pollfds, std::size(pollfds), kPollTimeoutMS));
LOG_ASSERT(poll_result >= 0) << "Polling on request fd failed.";
LOG_ASSERT(poll_result > 0) << "Polling on request fd timed out.";
LOG_ASSERT(pollfds[0].revents & POLLPRI)
<< "Polling on request fd exited with incorrect revents.";
}
bool V4L2IoctlShim::FindMediaDevice(struct v4l2_capability* cap) {
for (uint32_t i = 0; i < kMaximumDeviceNumber; ++i) {
media_fd_ = base::File(
base::FilePath(std::string(kMediaDevicePrefix) +
base::NumberToString(i)),
base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE);
if (!media_fd_.IsValid()) {
continue;
}
struct media_device_info media_info;
const bool ret = Ioctl(MEDIA_IOC_DEVICE_INFO, &media_info);
DCHECK(ret);
// Match the video device and the media controller by the |bus_info|
// field. This works better than |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*>(cap->bus_info)) > 0 &&
strlen(reinterpret_cast<const char*>(media_info.bus_info)) > 0 &&
!strcmp(reinterpret_cast<const char*>(cap->bus_info),
reinterpret_cast<const char*>(media_info.bus_info))) {
LOG(INFO) << "Using \"" << media_info.bus_info
<< "\" driver with /dev/media" << base::NumberToString(i)
<< ".";
return true;
}
// Fall back to matching the video device and the media controller by the
// |driver| field. This is needed because the mtk-vcodec driver does not
// always fill the |card| and |bus_info| fields properly.
if (!strcmp(reinterpret_cast<const char*>(cap->driver),
reinterpret_cast<const char*>(media_info.driver))) {
LOG(INFO) << "Using \"" << media_info.driver
<< "\" driver with /dev/media" << base::NumberToString(i)
<< ".";
return true;
}
media_fd_.Close();
}
return false;
}
bool V4L2IoctlShim::QueryFormat(enum v4l2_buf_type type,
uint32_t fourcc) const {
struct v4l2_fmtdesc fmtdesc;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.type = type;
while (Ioctl(VIDIOC_ENUM_FMT, &fmtdesc)) {
if (fourcc == fmtdesc.pixelformat)
return true;
fmtdesc.index++;
}
return false;
}
void V4L2IoctlShim::QueryAndMmapQueueBuffers(
std::unique_ptr<V4L2Queue>& queue) const {
DCHECK_EQ(queue->memory(), V4L2_MEMORY_MMAP);
MmappedBuffers buffers;
for (uint32_t i = 0; i < queue->num_buffers(); ++i) {
struct v4l2_buffer v4l_buffer;
std::vector<v4l2_plane> planes(VIDEO_MAX_PLANES);
memset(&v4l_buffer, 0, sizeof(v4l_buffer));
v4l_buffer.type = queue->type();
v4l_buffer.memory = queue->memory();
v4l_buffer.index = i;
v4l_buffer.length = queue->num_planes();
v4l_buffer.m.planes = planes.data();
const bool ret = Ioctl(VIDIOC_QUERYBUF, &v4l_buffer);
LOG_ASSERT(ret) << "VIDIOC_QUERYBUF for " << queue->type()
<< " queue failed";
buffers.emplace_back(base::MakeRefCounted<MmappedBuffer>(
decode_fd_.GetPlatformFile(), v4l_buffer));
}
queue->set_buffers(buffers);
}
} // namespace v4l2_test
} // namespace media