blob: 19c1313dcd61dab65df5ffff9ece9e99826975f3 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "TouchVideoDevice.h"
#define LOG_TAG "TouchVideoDevice"
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <iostream>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <log/log.h>
using android::base::StringPrintf;
using android::base::unique_fd;
namespace android {
TouchVideoDevice::TouchVideoDevice(int fd, std::string&& name, std::string&& devicePath,
uint32_t height, uint32_t width,
const std::array<const int16_t*, NUM_BUFFERS>& readLocations) :
mFd(fd), mName(std::move(name)), mPath(std::move(devicePath)),
mHeight(height), mWidth(width),
mReadLocations(readLocations) {
mFrames.reserve(MAX_QUEUE_SIZE);
};
std::unique_ptr<TouchVideoDevice> TouchVideoDevice::create(std::string devicePath) {
unique_fd fd(open(devicePath.c_str(), O_RDWR | O_NONBLOCK));
if (fd.get() == INVALID_FD) {
ALOGE("Could not open video device %s: %s", devicePath.c_str(), strerror(errno));
return nullptr;
}
struct v4l2_capability cap;
int result = ioctl(fd.get(), VIDIOC_QUERYCAP, &cap);
if (result == -1) {
ALOGE("VIDIOC_QUERYCAP failed: %s", strerror(errno));
return nullptr;
}
if (!(cap.capabilities & V4L2_CAP_TOUCH)) {
ALOGE("Capability V4L2_CAP_TOUCH is not present, can't use device for heatmap data. "
"Make sure device specifies V4L2_CAP_TOUCH");
return nullptr;
}
ALOGI("Opening video device: driver = %s, card = %s, bus_info = %s, version = %i",
cap.driver, cap.card, cap.bus_info, cap.version);
std::string name = reinterpret_cast<const char*>(cap.card);
struct v4l2_input v4l2_input_struct;
v4l2_input_struct.index = 0;
result = ioctl(fd.get(), VIDIOC_ENUMINPUT, &v4l2_input_struct);
if (result == -1) {
ALOGE("VIDIOC_ENUMINPUT failed: %s", strerror(errno));
return nullptr;
}
if (v4l2_input_struct.type != V4L2_INPUT_TYPE_TOUCH) {
ALOGE("Video device does not provide touch data. "
"Make sure device specifies V4L2_INPUT_TYPE_TOUCH.");
return nullptr;
}
struct v4l2_format v4l2_fmt;
v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
result = ioctl(fd.get(), VIDIOC_G_FMT, &v4l2_fmt);
if (result == -1) {
ALOGE("VIDIOC_G_FMT failed: %s", strerror(errno));
return nullptr;
}
const uint32_t height = v4l2_fmt.fmt.pix.height;
const uint32_t width = v4l2_fmt.fmt.pix.width;
ALOGI("Frame dimensions: height = %" PRIu32 " width = %" PRIu32, height, width);
struct v4l2_requestbuffers req = {};
req.count = NUM_BUFFERS;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
// req.reserved is zeroed during initialization, which is required per v4l docs
result = ioctl(fd.get(), VIDIOC_REQBUFS, &req);
if (result == -1) {
ALOGE("VIDIOC_REQBUFS failed: %s", strerror(errno));
return nullptr;
}
if (req.count != NUM_BUFFERS) {
ALOGE("Requested %zu buffers, but driver responded with count=%i", NUM_BUFFERS, req.count);
return nullptr;
}
struct v4l2_buffer buf = {};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
// buf.reserved and buf.reserved2 are zeroed during initialization, required per v4l docs
std::array<const int16_t*, NUM_BUFFERS> readLocations;
for (size_t i = 0; i < NUM_BUFFERS; i++) {
buf.index = i;
result = ioctl(fd.get(), VIDIOC_QUERYBUF, &buf);
if (result == -1) {
ALOGE("VIDIOC_QUERYBUF failed: %s", strerror(errno));
return nullptr;
}
if (buf.length != height * width * sizeof(int16_t)) {
ALOGE("Unexpected value of buf.length = %i (offset = %" PRIu32 ")",
buf.length, buf.m.offset);
return nullptr;
}
readLocations[i] = static_cast<const int16_t*>(mmap(nullptr /* start anywhere */,
buf.length, PROT_READ /* required */, MAP_SHARED /* recommended */,
fd.get(), buf.m.offset));
if (readLocations[i] == MAP_FAILED) {
ALOGE("%s: map failed: %s", __func__, strerror(errno));
return nullptr;
}
}
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
result = ioctl(fd.get(), VIDIOC_STREAMON, &type);
if (result == -1) {
ALOGE("VIDIOC_STREAMON failed: %s", strerror(errno));
return nullptr;
}
for (size_t i = 0; i < NUM_BUFFERS; i++) {
buf.index = i;
result = ioctl(fd.get(), VIDIOC_QBUF, &buf);
if (result == -1) {
ALOGE("VIDIOC_QBUF failed for buffer %zu: %s", i, strerror(errno));
return nullptr;
}
}
// Using 'new' to access a non-public constructor.
return std::unique_ptr<TouchVideoDevice>(new TouchVideoDevice(
fd.release(), std::move(name), std::move(devicePath), height, width, readLocations));
}
size_t TouchVideoDevice::readAndQueueFrames() {
std::vector<TouchVideoFrame> frames = readFrames();
const size_t numFrames = frames.size();
if (numFrames == 0) {
// Likely an error occurred
return 0;
}
// Concatenate the vectors, then clip up to maximum size allowed
mFrames.insert(mFrames.end(), std::make_move_iterator(frames.begin()),
std::make_move_iterator(frames.end()));
if (mFrames.size() > MAX_QUEUE_SIZE) {
ALOGE("More than %zu frames have been accumulated. Dropping %zu frames", MAX_QUEUE_SIZE,
mFrames.size() - MAX_QUEUE_SIZE);
mFrames.erase(mFrames.begin(), mFrames.end() - MAX_QUEUE_SIZE);
}
return numFrames;
}
std::vector<TouchVideoFrame> TouchVideoDevice::consumeFrames() {
std::vector<TouchVideoFrame> frames = std::move(mFrames);
mFrames = {};
return frames;
}
std::optional<TouchVideoFrame> TouchVideoDevice::readFrame() {
struct v4l2_buffer buf = {};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
int result = ioctl(mFd.get(), VIDIOC_DQBUF, &buf);
if (result == -1) {
// EAGAIN means we've reached the end of the read buffer, so it's expected.
if (errno != EAGAIN) {
ALOGE("VIDIOC_DQBUF failed: %s", strerror(errno));
}
return std::nullopt;
}
if ((buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) != V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC) {
// We use CLOCK_MONOTONIC for input events, so if the clocks don't match,
// we can't compare timestamps. Just log a warning, since this is a driver issue
ALOGW("The timestamp %ld.%ld was not acquired using CLOCK_MONOTONIC",
buf.timestamp.tv_sec, buf.timestamp.tv_usec);
}
std::vector<int16_t> data(mHeight * mWidth);
const int16_t* readFrom = mReadLocations[buf.index];
std::copy(readFrom, readFrom + mHeight * mWidth, data.begin());
TouchVideoFrame frame(mHeight, mWidth, std::move(data), buf.timestamp);
result = ioctl(mFd.get(), VIDIOC_QBUF, &buf);
if (result == -1) {
ALOGE("VIDIOC_QBUF failed: %s", strerror(errno));
}
return std::make_optional(std::move(frame));
}
/*
* This function should not be called unless buffer is ready! This must be checked with
* select, poll, epoll, or some other similar api first.
* The oldest frame will be at the beginning of the array.
*/
std::vector<TouchVideoFrame> TouchVideoDevice::readFrames() {
std::vector<TouchVideoFrame> frames;
while (true) {
std::optional<TouchVideoFrame> frame = readFrame();
if (!frame) {
break;
}
frames.push_back(std::move(*frame));
}
return frames;
}
TouchVideoDevice::~TouchVideoDevice() {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int result = ioctl(mFd.get(), VIDIOC_STREAMOFF, &type);
if (result == -1) {
ALOGE("VIDIOC_STREAMOFF failed: %s", strerror(errno));
}
for (const int16_t* buffer : mReadLocations) {
void* bufferAddress = static_cast<void*>(const_cast<int16_t*>(buffer));
result = munmap(bufferAddress, mHeight * mWidth * sizeof(int16_t));
if (result == -1) {
ALOGE("%s: Couldn't unmap: [%s]", __func__, strerror(errno));
}
}
}
std::string TouchVideoDevice::dump() const {
return StringPrintf("Video device %s (%s) : height=%" PRIu32 ", width=%" PRIu32
", fd=%i, hasValidFd=%s",
mName.c_str(), mPath.c_str(), mHeight, mWidth, mFd.get(),
hasValidFd() ? "true" : "false");
}
} // namespace android