blob: 395461e70d1c576be0944c90d93134e1bd965206 [file] [log] [blame]
/*
* v4l2_device.cpp - v4l2 device
*
* Copyright (c) 2014-2015 Intel Corporation
*
* 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.
*
* Author: Wind Yuan <feng.yuan@intel.com>
* Author: John Ye <john.ye@intel.com>
*/
#include "v4l2_device.h"
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <sys/mman.h>
#include "v4l2_buffer_proxy.h"
namespace XCam {
#define XCAM_V4L2_DEFAULT_BUFFER_COUNT 6
V4l2Device::V4l2Device (const char *name)
: _name (NULL)
, _fd (-1)
, _sensor_id (0)
, _capture_mode (0)
, _capture_buf_type (V4L2_BUF_TYPE_VIDEO_CAPTURE)
, _memory_type (V4L2_MEMORY_MMAP)
, _fps_n (0)
, _fps_d (0)
, _active (false)
, _buf_count (XCAM_V4L2_DEFAULT_BUFFER_COUNT)
{
if (name)
_name = strndup (name, XCAM_MAX_STR_SIZE);
xcam_mem_clear (_format);
}
V4l2Device::~V4l2Device ()
{
close();
if (_name)
xcam_free (_name);
}
bool
V4l2Device::set_device_name (const char *name)
{
XCAM_ASSERT (name);
if (is_opened()) {
XCAM_LOG_WARNING ("can't set device name since device opened");
return false;
}
if (_name)
xcam_free (_name);
_name = strndup (name, XCAM_MAX_STR_SIZE);
return true;
}
bool
V4l2Device::set_sensor_id (int id)
{
if (is_opened()) {
XCAM_LOG_WARNING ("can't set sensor id since device opened");
return false;
}
_sensor_id = id;
return true;
}
bool
V4l2Device::set_capture_mode (uint32_t capture_mode)
{
if (is_opened()) {
XCAM_LOG_WARNING ("can't set sensor id since device opened");
return false;
}
_capture_mode = capture_mode;
return true;
}
bool
V4l2Device::set_framerate (uint32_t n, uint32_t d)
{
if (_format.fmt.pix.pixelformat) {
XCAM_LOG_WARNING ("device(%s) set framerate failed since formatted was already set.", XCAM_STR(_name));
return false;
}
_fps_n = n;
_fps_d = d;
return true;
}
void
V4l2Device::get_framerate (uint32_t &n, uint32_t &d)
{
n = _fps_n;
d = _fps_d;
}
bool
V4l2Device::set_mem_type (enum v4l2_memory type) {
if (is_activated ()) {
XCAM_LOG_WARNING ("device(%s) set mem type failed", XCAM_STR (_name));
return false;
}
_memory_type = type;
return true;
}
bool
V4l2Device::set_buffer_count (uint32_t buf_count)
{
if (is_activated ()) {
XCAM_LOG_WARNING ("device(%s) set buffer count failed", XCAM_STR (_name));
return false;
}
_buf_count = buf_count;
return true;
}
XCamReturn
V4l2Device::open ()
{
struct v4l2_streamparm param;
if (is_opened()) {
XCAM_LOG_DEBUG ("device(%s) was already opened", XCAM_STR(_name));
return XCAM_RETURN_NO_ERROR;
}
if (!_name) {
XCAM_LOG_DEBUG ("v4l2 device open failed, there's no device name");
return XCAM_RETURN_ERROR_PARAM;
}
_fd = ::open (_name, O_RDWR);
if (_fd == -1) {
XCAM_LOG_DEBUG ("open device(%s) failed", _name);
return XCAM_RETURN_ERROR_IOCTL;
}
// set sensor id
if (io_control (VIDIOC_S_INPUT, &_sensor_id) < 0) {
XCAM_LOG_WARNING ("set sensor id(%d) failed but continue", _sensor_id);
}
// set capture mode
xcam_mem_clear (param);
param.type = _capture_buf_type;
param.parm.capture.capturemode = _capture_mode;
if (io_control (VIDIOC_S_PARM, &param) < 0) {
XCAM_LOG_WARNING ("set capture mode(0x%08x) failed but continue", _capture_mode);
}
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::close ()
{
if (!is_opened())
return XCAM_RETURN_NO_ERROR;
::close (_fd);
_fd = -1;
return XCAM_RETURN_NO_ERROR;
}
int
V4l2Device::io_control (int cmd, void *arg)
{
if (_fd <= 0)
return -1;
return xcam_device_ioctl (_fd, cmd, arg);
}
int
V4l2Device::poll_event (int timeout_msec)
{
struct pollfd poll_fd;
int ret = 0;
XCAM_ASSERT (_fd > 0);
xcam_mem_clear (poll_fd);
poll_fd.fd = _fd;
poll_fd.events = (POLLPRI | POLLIN | POLLERR | POLLNVAL | POLLHUP);
ret = poll (&poll_fd, 1, timeout_msec);
if (ret > 0 && (poll_fd.revents & (POLLERR | POLLNVAL | POLLHUP))) {
XCAM_LOG_DEBUG ("v4l2 subdev(%s) polled error", XCAM_STR(_name));
return -1;
}
return ret;
}
XCamReturn
V4l2Device::set_format (struct v4l2_format &format)
{
XCamReturn ret = XCAM_RETURN_NO_ERROR;
XCAM_FAIL_RETURN (ERROR, !is_activated (), XCAM_RETURN_ERROR_PARAM,
"Cannot set format to v4l2 device while it is active.");
XCAM_FAIL_RETURN (ERROR, is_opened (), XCAM_RETURN_ERROR_FILE,
"Cannot set format to v4l2 device while it is closed.");
struct v4l2_format tmp_format = format;
ret = pre_set_format (format);
if (ret != XCAM_RETURN_NO_ERROR) {
XCAM_LOG_WARNING ("device(%s) pre_set_format failed", XCAM_STR (_name));
return ret;
}
if (io_control (VIDIOC_S_FMT, &format) < 0) {
if (errno == EBUSY) {
// TODO log device name
XCAM_LOG_ERROR("Video device is busy, fail to set format.");
} else {
// TODO log format details and errno
XCAM_LOG_ERROR("Fail to set format: %s", strerror(errno));
}
return XCAM_RETURN_ERROR_IOCTL;
}
if (tmp_format.fmt.pix.width != format.fmt.pix.width || tmp_format.fmt.pix.height != format.fmt.pix.height) {
XCAM_LOG_ERROR (
"device(%s) set v4l2 format failed, supported format: width:%d, height:%d",
XCAM_STR (_name),
format.fmt.pix.width,
format.fmt.pix.height);
return XCAM_RETURN_ERROR_PARAM;
}
while (_fps_n && _fps_d) {
struct v4l2_streamparm param;
xcam_mem_clear (param);
param.type = _capture_buf_type;
if (io_control (VIDIOC_G_PARM, &param) < 0) {
XCAM_LOG_WARNING ("device(%s) set framerate failed on VIDIOC_G_PARM but continue", XCAM_STR (_name));
break;
}
if (!(param.parm.capture.capability & V4L2_CAP_TIMEPERFRAME))
break;
param.parm.capture.timeperframe.numerator = _fps_d;
param.parm.capture.timeperframe.denominator = _fps_n;
if (io_control (VIDIOC_S_PARM, &param) < 0) {
XCAM_LOG_WARNING ("device(%s) set framerate failed on VIDIOC_S_PARM but continue", XCAM_STR (_name));
break;
}
_fps_n = param.parm.capture.timeperframe.denominator;
_fps_d = param.parm.capture.timeperframe.numerator;
XCAM_LOG_INFO ("device(%s) set framerate(%d/%d)", XCAM_STR (_name), _fps_n, _fps_d);
// exit here, otherwise it is an infinite loop
break;
}
ret = post_set_format (format);
if (ret != XCAM_RETURN_NO_ERROR) {
XCAM_LOG_WARNING ("device(%s) post_set_format failed", XCAM_STR (_name));
return ret;
}
_format = format;
XCAM_LOG_INFO (
"device(%s) set format(w:%d, h:%d, pixelformat:%s, bytesperline:%d,image_size:%d)",
XCAM_STR (_name),
format.fmt.pix.width, format.fmt.pix.height,
xcam_fourcc_to_string (format.fmt.pix.pixelformat),
format.fmt.pix.bytesperline,
format.fmt.pix.sizeimage);
return XCAM_RETURN_NO_ERROR;
}
/*! \brief v4l2 set format
*
* \param[in] width format width
* \param[in] height format height
* \param[in] pixelformat fourcc
* \param[in] field V4L2_FIELD_INTERLACED or V4L2_FIELD_NONE
*/
XCamReturn
V4l2Device::set_format (
uint32_t width, uint32_t height,
uint32_t pixelformat, enum v4l2_field field, uint32_t bytes_perline)
{
struct v4l2_format format;
xcam_mem_clear (format);
format.type = _capture_buf_type;
format.fmt.pix.width = width;
format.fmt.pix.height = height;
format.fmt.pix.pixelformat = pixelformat;
format.fmt.pix.field = field;
if (bytes_perline != 0)
format.fmt.pix.bytesperline = bytes_perline;
return set_format (format);
}
XCamReturn
V4l2Device::pre_set_format (struct v4l2_format &format)
{
XCAM_UNUSED (format);
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::post_set_format (struct v4l2_format &format)
{
XCAM_UNUSED (format);
return XCAM_RETURN_NO_ERROR;
}
std::list<struct v4l2_fmtdesc>
V4l2Device::enum_formats ()
{
std::list<struct v4l2_fmtdesc> formats;
struct v4l2_fmtdesc format;
uint32_t i = 0;
while (1) {
xcam_mem_clear (format);
format.index = i++;
format.type = _capture_buf_type;
if (this->io_control (VIDIOC_ENUM_FMT, &format) < 0) {
if (errno == EINVAL)
break;
else { // error
XCAM_LOG_DEBUG ("enum formats failed");
return formats;
}
}
formats.push_back (format);
}
return formats;
}
XCamReturn
V4l2Device::get_format (struct v4l2_format &format)
{
if (is_activated ()) {
format = _format;
return XCAM_RETURN_NO_ERROR;
}
if (!is_opened ())
return XCAM_RETURN_ERROR_IOCTL;
xcam_mem_clear (format);
format.type = _capture_buf_type;
if (this->io_control (VIDIOC_G_FMT, &format) < 0) {
// FIXME: also log the device name?
XCAM_LOG_ERROR("Fail to get format via ioctl VIDVIO_G_FMT.");
return XCAM_RETURN_ERROR_IOCTL;
}
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::start ()
{
XCamReturn ret = XCAM_RETURN_NO_ERROR;
// request buffer first
ret = request_buffer ();
XCAM_FAIL_RETURN (
ERROR, ret == XCAM_RETURN_NO_ERROR, ret,
"device(%s) start failed", XCAM_STR (_name));
//alloc buffers
ret = init_buffer_pool ();
XCAM_FAIL_RETURN (
ERROR, ret == XCAM_RETURN_NO_ERROR, ret,
"device(%s) start failed", XCAM_STR (_name));
//queue all buffers
for (uint32_t i = 0; i < _buf_count; ++i) {
SmartPtr<V4l2Buffer> &buf = _buf_pool [i];
XCAM_ASSERT (buf.ptr());
XCAM_ASSERT (buf->get_buf().index == i);
ret = queue_buffer (buf);
if (ret != XCAM_RETURN_NO_ERROR) {
XCAM_LOG_ERROR (
"device(%s) start failed on queue index:%d",
XCAM_STR (_name), i);
stop ();
return ret;
}
}
// stream on
if (io_control (VIDIOC_STREAMON, &_capture_buf_type) < 0) {
XCAM_LOG_ERROR (
"device(%s) start failed on VIDIOC_STREAMON",
XCAM_STR (_name));
stop ();
return XCAM_RETURN_ERROR_IOCTL;
}
_active = true;
XCAM_LOG_INFO ("device(%s) started successfully", XCAM_STR (_name));
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::stop ()
{
// stream off
if (_active) {
if (io_control (VIDIOC_STREAMOFF, &_capture_buf_type) < 0) {
XCAM_LOG_WARNING ("device(%s) steamoff failed", XCAM_STR (_name));
}
_active = false;
}
fini_buffer_pool ();
XCAM_LOG_INFO ("device(%s) stopped", XCAM_STR (_name));
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::request_buffer ()
{
struct v4l2_requestbuffers request_buf;
XCAM_ASSERT (!is_activated());
xcam_mem_clear (request_buf);
request_buf.type = _capture_buf_type;
request_buf.count = _buf_count;
request_buf.memory = _memory_type;
if (io_control (VIDIOC_REQBUFS, &request_buf) < 0) {
XCAM_LOG_INFO ("device(%s) starts failed on VIDIOC_REQBUFS", XCAM_STR (_name));
return XCAM_RETURN_ERROR_IOCTL;
}
if (request_buf.count != _buf_count) {
XCAM_LOG_DEBUG (
"device(%s) request buffer count doesn't match user settings, reset buffer count to %d",
XCAM_STR (_name), request_buf.count);
_buf_count = request_buf.count;
}
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::allocate_buffer (
SmartPtr<V4l2Buffer> &buf,
const struct v4l2_format &format,
const uint32_t index)
{
struct v4l2_buffer v4l2_buf;
xcam_mem_clear (v4l2_buf);
v4l2_buf.index = index;
v4l2_buf.type = _capture_buf_type;
v4l2_buf.memory = _memory_type;
switch (_memory_type) {
case V4L2_MEMORY_DMABUF:
{
struct v4l2_exportbuffer expbuf;
xcam_mem_clear (expbuf);
expbuf.type = _capture_buf_type;
expbuf.index = index;
expbuf.flags = O_CLOEXEC;
if (io_control (VIDIOC_EXPBUF, &expbuf) < 0) {
XCAM_LOG_WARNING ("device(%s) get dma buf(%d) failed", XCAM_STR (_name), index);
return XCAM_RETURN_ERROR_MEM;
}
v4l2_buf.m.fd = expbuf.fd;
v4l2_buf.length = format.fmt.pix.sizeimage;
}
break;
case V4L2_MEMORY_MMAP:
{
void *pointer;
int map_flags = MAP_SHARED;
#ifdef NEED_MAP_32BIT
map_flags |= MAP_32BIT;
#endif
if (io_control (VIDIOC_QUERYBUF, &v4l2_buf) < 0) {
XCAM_LOG_WARNING("device(%s) query MMAP buf(%d) failed", XCAM_STR(_name), index);
return XCAM_RETURN_ERROR_MEM;
}
pointer = mmap (0, v4l2_buf.length, PROT_READ | PROT_WRITE, map_flags, _fd, v4l2_buf.m.offset);
if (pointer == MAP_FAILED) {
XCAM_LOG_WARNING("device(%s) mmap buf(%d) failed", XCAM_STR(_name), index);
return XCAM_RETURN_ERROR_MEM;
}
v4l2_buf.m.userptr = (uintptr_t) pointer;
}
break;
case V4L2_MEMORY_USERPTR:
default:
XCAM_ASSERT (false);
XCAM_LOG_WARNING (
"device(%s) allocated buffer mem_type(%d) doesn't support",
XCAM_STR (_name), _memory_type);
return XCAM_RETURN_ERROR_MEM;
}
buf = new V4l2Buffer (v4l2_buf, _format);
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::init_buffer_pool ()
{
XCamReturn ret = XCAM_RETURN_NO_ERROR;
uint32_t i = 0;
_buf_pool.clear ();
_buf_pool.reserve (_buf_count);
for (; i < _buf_count; i++) {
SmartPtr<V4l2Buffer> new_buf;
ret = allocate_buffer (new_buf, _format, i);
if (ret != XCAM_RETURN_NO_ERROR) {
break;
}
_buf_pool.push_back (new_buf);
}
if (_buf_pool.empty()) {
XCAM_LOG_ERROR ("No bufer allocated in device(%s)", XCAM_STR (_name));
return XCAM_RETURN_ERROR_MEM;
}
if (i != _buf_count) {
XCAM_LOG_WARNING (
"device(%s) allocate buffer count:%d failback to %d",
XCAM_STR (_name), _buf_count, i);
_buf_count = i;
}
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::fini_buffer_pool()
{
_buf_pool.clear ();
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::dequeue_buffer(SmartPtr<V4l2Buffer> &buf)
{
struct v4l2_buffer v4l2_buf;
if (!is_activated()) {
XCAM_LOG_DEBUG (
"device(%s) dequeue buffer failed since not activated", XCAM_STR (_name));
return XCAM_RETURN_ERROR_PARAM;
}
xcam_mem_clear (v4l2_buf);
v4l2_buf.type = _capture_buf_type;
v4l2_buf.memory = _memory_type;
if (this->io_control (VIDIOC_DQBUF, &v4l2_buf) < 0) {
XCAM_LOG_ERROR ("device(%s) fail to dequeue buffer.", XCAM_STR (_name));
return XCAM_RETURN_ERROR_IOCTL;
}
XCAM_LOG_DEBUG ("device(%s) dequeue buffer index:%d", XCAM_STR (_name), v4l2_buf.index);
if (v4l2_buf.index > _buf_count) {
XCAM_LOG_ERROR (
"device(%s) dequeue wrong buffer index:%d",
XCAM_STR (_name), v4l2_buf.index);
return XCAM_RETURN_ERROR_ISP;
}
buf = _buf_pool [v4l2_buf.index];
buf->set_timestamp (v4l2_buf.timestamp);
buf->set_timecode (v4l2_buf.timecode);
buf->set_sequence (v4l2_buf.sequence);
//buf.set_length (v4l2_buf.length); // not necessary to set length
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2Device::queue_buffer (SmartPtr<V4l2Buffer> &buf)
{
XCAM_ASSERT (buf.ptr());
buf->reset ();
struct v4l2_buffer v4l2_buf = buf->get_buf ();
XCAM_ASSERT (v4l2_buf.index < _buf_count);
XCAM_LOG_DEBUG ("device(%s) queue buffer index:%d", XCAM_STR (_name), v4l2_buf.index);
if (io_control (VIDIOC_QBUF, &v4l2_buf) < 0) {
XCAM_LOG_ERROR("fail to enqueue buffer index:%d.", v4l2_buf.index);
return XCAM_RETURN_ERROR_IOCTL;
}
return XCAM_RETURN_NO_ERROR;
}
V4l2SubDevice::V4l2SubDevice (const char *name)
: V4l2Device (name)
{
}
XCamReturn
V4l2SubDevice::subscribe_event (int event)
{
struct v4l2_event_subscription sub;
int ret = 0;
XCAM_ASSERT (is_opened());
xcam_mem_clear (sub);
sub.type = event;
ret = this->io_control (VIDIOC_SUBSCRIBE_EVENT, &sub);
if (ret < 0) {
XCAM_LOG_DEBUG ("subdev(%s) subscribe event(%d) failed", XCAM_STR(_name), event);
return XCAM_RETURN_ERROR_IOCTL;
}
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2SubDevice::unsubscribe_event (int event)
{
struct v4l2_event_subscription sub;
int ret = 0;
XCAM_ASSERT (is_opened());
xcam_mem_clear (sub);
sub.type = event;
ret = this->io_control (VIDIOC_UNSUBSCRIBE_EVENT, &sub);
if (ret < 0) {
XCAM_LOG_DEBUG ("subdev(%s) unsubscribe event(%d) failed", XCAM_STR(_name), event);
return XCAM_RETURN_ERROR_IOCTL;
}
return XCAM_RETURN_NO_ERROR;
}
XCamReturn
V4l2SubDevice::dequeue_event (struct v4l2_event &event)
{
int ret = 0;
XCAM_ASSERT (is_opened());
ret = this->io_control (VIDIOC_DQEVENT, &event);
if (ret < 0) {
XCAM_LOG_DEBUG ("subdev(%s) dequeue event failed", XCAM_STR(_name));
return XCAM_RETURN_ERROR_IOCTL;
}
return XCAM_RETURN_NO_ERROR;
}
XCamReturn V4l2SubDevice::start ()
{
if (!is_opened())
return XCAM_RETURN_ERROR_PARAM;
_active = true;
return XCAM_RETURN_NO_ERROR;
}
XCamReturn V4l2SubDevice::stop ()
{
if (_active)
_active = false;
return XCAM_RETURN_NO_ERROR;
}
};