/* * 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 * Author: John Ye */ #include "v4l2_device.h" #include #include #include #include #include #include #include #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, ¶m) < 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, ¶m) < 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, ¶m) < 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 V4l2Device::enum_formats () { std::list 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 &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 &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 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 &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 &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; } };