blob: aee87f0da225d584f8d4e6a701a87a3b0dfe1122 [file] [log] [blame]
// Copyright 2021 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.
#include "media/gpu/v4l2/test/v4l2_ioctl_shim.h"
#include <linux/media.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unordered_map>
#include "base/containers/contains.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "media/base/video_types.h"
namespace media {
namespace v4l2_test {
constexpr int kIoctlOk = 0;
static const base::FilePath kDecodeDevice("/dev/video-dec0");
static const base::FilePath kMediaDevice("/dev/media0");
#define V4L2_REQUEST_CODE_AND_STRING(x) \
{ x, #x }
// 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_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_STREAMON),
V4L2_REQUEST_CODE_AND_STRING(MEDIA_IOC_REQUEST_ALLOC)};
// 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 << 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) {
PLOG_IF(ERROR, ret != kIoctlOk)
<< "Ioctl request failed for " << V4L2RequestCodeToString(request_code)
<< ".";
VLOG_IF(4, ret == kIoctlOk)
<< V4L2RequestCodeToString(request_code) << " succeeded.";
}
V4L2Queue::V4L2Queue(enum v4l2_buf_type type,
uint32_t fourcc,
const gfx::Size& size,
uint32_t num_planes,
enum v4l2_memory memory)
: type_(type),
fourcc_(fourcc),
display_size_(size),
num_planes_(num_planes),
memory_(memory) {}
V4L2Queue::~V4L2Queue() = default;
V4L2IoctlShim::V4L2IoctlShim()
: decode_fd_(kDecodeDevice,
base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WRITE),
media_fd_(kMediaDevice,
base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WRITE) {
PCHECK(decode_fd_.IsValid()) << "Failed to open " << kDecodeDevice;
PCHECK(media_fd_.IsValid()) << "Failed to open " << kMediaDevice;
}
V4L2IoctlShim::~V4L2IoctlShim() = default;
template <typename T>
bool V4L2IoctlShim::Ioctl(int request_code, T* argp) const {
NOTREACHED() << "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.\n";
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_fmtdesc* fmtdesc) const {
DCHECK_EQ(request_code, static_cast<int>(VIDIOC_ENUM_FMT));
LOG_ASSERT(fmtdesc != nullptr) << "|fmtdesc| check failed.\n";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, fmtdesc);
LogIoctlResult(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.\n";
const int ret = ioctl(decode_fd_.GetPlatformFile(), request_code, frame_size);
LogIoctlResult(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.\n";
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.\n";
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));
LOG_ASSERT(buffer != nullptr) << "|buffer| check failed.\n";
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>(MEDIA_IOC_REQUEST_ALLOC));
LOG_ASSERT(arg != nullptr) << "|arg| check failed.\n";
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;
}
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);
}
bool V4L2IoctlShim::SetFmt(const std::unique_ptr<V4L2Queue>& queue) const {
struct v4l2_format fmt;
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->display_size().width();
fmt.fmt.pix_mp.height = queue->display_size().height();
const bool ret = Ioctl(VIDIOC_S_FMT, &fmt);
LOG(INFO) << queue->type() << " - VIDIOC_S_FMT: " << fmt.fmt.pix_mp;
return ret;
}
bool V4L2IoctlShim::GetFmt(const enum v4l2_buf_type type,
gfx::Size* coded_size,
uint32_t* num_planes) const {
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = type;
const bool ret = Ioctl(VIDIOC_G_FMT, &fmt);
coded_size->SetSize(fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height);
*num_planes = fmt.fmt.pix_mp.num_planes;
LOG(INFO) << type << " - VIDIOC_G_FMT: " << fmt.fmt.pix_mp;
return ret;
}
bool V4L2IoctlShim::TryFmt(const std::unique_ptr<V4L2Queue>& queue) const {
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = queue->type();
fmt.fmt.pix_mp.pixelformat = queue->fourcc();
fmt.fmt.pix_mp.num_planes = queue->num_planes();
fmt.fmt.pix_mp.width = queue->coded_size().width();
fmt.fmt.pix_mp.height = queue->coded_size().height();
const bool ret = Ioctl(VIDIOC_TRY_FMT, &fmt);
LOG(INFO) << queue->type() << " - VIDIOC_TRY_FMT: " << fmt.fmt.pix_mp;
return ret;
}
bool V4L2IoctlShim::ReqBufs(const std::unique_ptr<V4L2Queue>& queue) const {
struct v4l2_requestbuffers reqbuf;
memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.count = kRequestBufferCount;
reqbuf.type = queue->type();
reqbuf.memory = queue->memory();
const bool ret = Ioctl(VIDIOC_REQBUFS, &reqbuf);
LOG(INFO) << kRequestBufferCount << " buffers requested, " << reqbuf.count
<< " buffers returned for " << queue->type() << ".";
return ret;
}
bool V4L2IoctlShim::QBuf(const std::unique_ptr<V4L2Queue>& queue,
const uint32_t index) const {
LOG_ASSERT(queue->memory() == V4L2_MEMORY_MMAP)
<< "Only V4L2_MEMORY_MMAP is currently supported.\n";
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 = index;
v4l2_buffer.m.planes = planes.data();
v4l2_buffer.length = queue->num_planes();
MmapedBuffers buffers = queue->buffers();
for (uint32_t i = 0; i < queue->num_planes(); ++i) {
v4l2_buffer.m.planes[i].length = buffers[index][i].length;
v4l2_buffer.m.planes[i].bytesused = buffers[index][i].length;
v4l2_buffer.m.planes[i].data_offset = 0;
}
return Ioctl(VIDIOC_QBUF, &v4l2_buffer);
}
bool V4L2IoctlShim::StreamOn(const enum v4l2_buf_type type) const {
int arg = static_cast<int>(type);
return Ioctl(VIDIOC_STREAMON, &arg);
}
bool V4L2IoctlShim::MediaIocRequestAlloc() const {
// TODO(stevecho): need to use the file descriptor representing the request
// for MEDIA_REQUEST_IOC_QUEUE() call to queue to the request.
int req_fd;
return Ioctl(MEDIA_IOC_REQUEST_ALLOC, &req_fd);
}
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;
}
bool V4L2IoctlShim::VerifyCapabilities(uint32_t compressed_format,
uint32_t uncompressed_format) const {
struct v4l2_capability cap;
memset(&cap, 0, sizeof(cap));
DCHECK(Ioctl(VIDIOC_QUERYCAP, &cap));
LOG(INFO) << "Driver=\"" << cap.driver << "\" bus_info=\"" << cap.bus_info
<< "\" card=\"" << cap.card;
const bool is_compressed_format_supported =
QueryFormat(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, compressed_format);
LOG_IF(ERROR, !is_compressed_format_supported)
<< media::FourccToString(compressed_format)
<< " is not a supported compressed OUTPUT format.";
const bool is_uncompressed_format_supported =
QueryFormat(V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, uncompressed_format);
LOG_IF(ERROR, !is_uncompressed_format_supported)
<< media::FourccToString(uncompressed_format)
<< " is not a supported uncompressed CAPTURE format.";
return is_compressed_format_supported && is_uncompressed_format_supported;
}
bool V4L2IoctlShim::QueryAndMmapQueueBuffers(
const std::unique_ptr<V4L2Queue>& queue) const {
DCHECK_EQ(queue->memory(), V4L2_MEMORY_MMAP);
auto buffers = queue->buffers();
for (uint32_t i = 0; i < kRequestBufferCount; ++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();
DCHECK(Ioctl(VIDIOC_QUERYBUF, &v4l_buffer));
for (uint32_t j = 0; j < queue->num_planes(); ++j) {
buffers[i][j].length = v4l_buffer.m.planes[j].length;
buffers[i][j].start =
mmap(NULL, v4l_buffer.m.planes[j].length, PROT_READ | PROT_WRITE,
MAP_SHARED, decode_fd_.GetPlatformFile(),
v4l_buffer.m.planes[j].m.mem_offset);
if (buffers[i][j].start == MAP_FAILED) {
LOG(ERROR) << "Failed to mmap buffer of length("
<< v4l_buffer.m.planes[j].length << ") and offset("
<< std::hex << v4l_buffer.m.planes[j].m.mem_offset << ").";
return false;
}
}
}
return true;
}
} // namespace v4l2_test
} // namespace media