// Copyright 2019 Fuzhou Rockchip Electronics Co., Ltd. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include "buffer.h" #include "utils.h" #include "v4l2_stream.h" namespace easymedia { class V4L2CaptureStream : public V4L2Stream { public: V4L2CaptureStream(const char* param); virtual ~V4L2CaptureStream() { V4L2CaptureStream::Close(); assert(!started); } static const char* GetStreamName() { return "v4l2_capture_stream"; } virtual std::shared_ptr Read(); virtual int Open() final; virtual int Close() final; private: int BufferExport(enum v4l2_buf_type bt, int index, int* dmafd); enum v4l2_memory memory_type; std::string data_type; PixelFormat pix_fmt; int width, height; int colorspace; int loop_num; int quantization; std::vector buffer_vec; bool started; }; V4L2CaptureStream::V4L2CaptureStream(const char* param) : V4L2Stream(param), memory_type(V4L2_MEMORY_MMAP), data_type(IMAGE_NV12), pix_fmt(PIX_FMT_NONE), width(0), height(0), colorspace(-1), loop_num(2), quantization(-1), started(false) { if (device.empty()) { return; } std::map params; std::list> req_list; std::string mem_type, str_loop_num; std::string str_width, str_height, str_color_space, str_quantization; req_list.push_back(std::pair(KEY_V4L2_MEM_TYPE, mem_type)); req_list.push_back(std::pair(KEY_FRAMES, str_loop_num)); req_list.push_back(std::pair(KEY_OUTPUTDATATYPE, data_type)); req_list.push_back(std::pair(KEY_BUFFER_WIDTH, str_width)); req_list.push_back(std::pair(KEY_BUFFER_HEIGHT, str_height)); req_list.push_back(std::pair(KEY_V4L2_COLORSPACE, str_color_space)); req_list.push_back(std::pair(KEY_V4L2_QUANTIZATION, str_quantization)); int ret = parse_media_param_match(param, params, req_list); if (ret == 0) { return; } if (!mem_type.empty()) { memory_type = static_cast(GetV4L2Type(mem_type.c_str())); } if (!str_loop_num.empty()) { loop_num = std::stoi(str_loop_num); } assert(loop_num >= 2); if (!str_width.empty()) { width = std::stoi(str_width); } if (!str_height.empty()) { height = std::stoi(str_height); } if (!str_color_space.empty()) { colorspace = std::stoi(str_color_space); } if (!str_quantization.empty()) { quantization = std::stoi(str_quantization); } } int V4L2CaptureStream::BufferExport(enum v4l2_buf_type bt, int index, int* dmafd) { struct v4l2_exportbuffer expbuf; memset(&expbuf, 0, sizeof(expbuf)); expbuf.type = bt; expbuf.index = index; if (v4l2_ctx->IoCtrl(VIDIOC_EXPBUF, &expbuf) == -1) { LOG("VIDIOC_EXPBUF %d failed, %m\n", index); return -1; } *dmafd = expbuf.fd; return 0; } class V4L2Buffer { public: V4L2Buffer() : dmafd(-1), ptr(nullptr), length(0), munmap_f(nullptr) { } ~V4L2Buffer() { if (dmafd >= 0) { close(dmafd); } if (ptr && ptr != MAP_FAILED && munmap_f) { munmap_f(ptr, length); } } int dmafd; void* ptr; size_t length; int (*munmap_f)(void* _start, size_t length); }; static int __free_v4l2buffer(void* arg) { delete static_cast(arg); return 0; } int V4L2CaptureStream::Open() { const char* dev = device.c_str(); if (width <= 0 || height <= 0) { LOG("Invalid param, device=%s, width=%d, height=%d\n", dev, width, height); return -EINVAL; } int ret = V4L2Stream::Open(); if (ret) { return ret; } struct v4l2_capability cap; memset(&cap, 0, sizeof(cap)); if (v4l2_ctx->IoCtrl(VIDIOC_QUERYCAP, &cap) < 0) { LOG("Failed to ioctl(VIDIOC_QUERYCAP): %m\n"); return -1; } if ((capture_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) && !(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { LOG("%s, Not a video capture device.\n", dev); return -1; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { LOG("%s does not support the streaming I/O method.\n", dev); return -1; } const char* data_type_str = data_type.c_str(); struct v4l2_format fmt; memset(&fmt, 0, sizeof(fmt)); fmt.type = capture_type; fmt.fmt.pix.width = width; fmt.fmt.pix.height = height; fmt.fmt.pix.pixelformat = GetV4L2FmtByString(data_type_str); fmt.fmt.pix.field = V4L2_FIELD_ANY; if (quantization >= 0) { fmt.fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC; fmt.fmt.pix.quantization = quantization; } if (colorspace >= 0) { fmt.fmt.pix.colorspace = colorspace; } if (fmt.fmt.pix.pixelformat == 0) { LOG("unsupport input format : %s\n", data_type_str); return -1; } if (v4l2_ctx->IoCtrl(VIDIOC_S_FMT, &fmt) < 0) { LOG("%s, s fmt failed(cap type=%d, %c%c%c%c), %m\n", dev, capture_type, DUMP_FOURCC(fmt.fmt.pix.pixelformat)); return -1; } if (GetV4L2FmtByString(data_type_str) != fmt.fmt.pix.pixelformat) { LOG("%s, expect %s, return %c%c%c%c\n", dev, data_type_str, DUMP_FOURCC(fmt.fmt.pix.pixelformat)); return -1; } pix_fmt = StringToPixFmt(data_type_str); if (width != (int)fmt.fmt.pix.width || height != (int)fmt.fmt.pix.height) { LOG("%s change res from %dx%d to %dx%d\n", dev, width, height, fmt.fmt.pix.width, fmt.fmt.pix.height); width = fmt.fmt.pix.width; height = fmt.fmt.pix.height; return -1; } if (fmt.fmt.pix.field == V4L2_FIELD_INTERLACED) { LOG("%s is using the interlaced mode\n", dev); } struct v4l2_requestbuffers req; req.type = capture_type; req.count = loop_num; req.memory = memory_type; if (v4l2_ctx->IoCtrl(VIDIOC_REQBUFS, &req) < 0) { LOG("%s, count=%d, ioctl(VIDIOC_REQBUFS): %m\n", dev, loop_num); return -1; } int w = UPALIGNTO16(width); int h = UPALIGNTO16(height); if (memory_type == V4L2_MEMORY_DMABUF) { int size = 0; if (pix_fmt != PIX_FMT_NONE) { size = CalPixFmtSize(pix_fmt, w, h, 16); } if (size == 0) { // unknown pixel format size = w * h * 4; } for (size_t i = 0; i < req.count; i++) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.type = req.type; buf.index = i; buf.memory = req.memory; auto&& buffer = MediaBuffer::Alloc2(size, MediaBuffer::MemType::MEM_HARD_WARE); if (buffer.GetSize() == 0) { errno = ENOMEM; return -1; } buffer_vec.push_back(buffer); buf.m.fd = buffer.GetFD(); buf.length = buffer.GetSize(); if (v4l2_ctx->IoCtrl(VIDIOC_QBUF, &buf) < 0) { LOG("%s ioctl(VIDIOC_QBUF): %m\n", dev); return -1; } } } else if (memory_type == V4L2_MEMORY_MMAP) { for (size_t i = 0; i < req.count; i++) { struct v4l2_buffer buf; void* ptr = MAP_FAILED; V4L2Buffer* buffer = new V4L2Buffer(); if (!buffer) { errno = ENOMEM; return -1; } buffer_vec.push_back(MediaBuffer(nullptr, 0, -1, buffer, __free_v4l2buffer)); memset(&buf, 0, sizeof(buf)); buf.type = req.type; buf.index = i; buf.memory = req.memory; if (v4l2_ctx->IoCtrl(VIDIOC_QUERYBUF, &buf) < 0) { LOG("%s ioctl(VIDIOC_QUERYBUF): %m\n", dev); return -1; } ptr = v4l2_mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (ptr == MAP_FAILED) { LOG("%s v4l2_mmap (%d): %m\n", dev, (int)i); return -1; } MediaBuffer& mb = buffer_vec[i]; buffer->munmap_f = vio.munmap_f; buffer->ptr = ptr; mb.SetPtr(ptr); buffer->length = buf.length; mb.SetSize(buf.length); LOGD("query buf.length=%d\n", (int)buf.length); } for (size_t i = 0; i < req.count; ++i) { struct v4l2_buffer buf; int dmafd = -1; memset(&buf, 0, sizeof(buf)); buf.type = req.type; buf.memory = req.memory; buf.index = i; if (v4l2_ctx->IoCtrl(VIDIOC_QBUF, &buf) < 0) { LOG("%s, ioctl(VIDIOC_QBUF): %m\n", dev); return -1; } if (!BufferExport(capture_type, i, &dmafd)) { MediaBuffer& mb = buffer_vec[i]; V4L2Buffer* buffer = static_cast(mb.GetUserData().get()); buffer->dmafd = dmafd; mb.SetFD(dmafd); } } } SetReadable(true); return 0; } int V4L2CaptureStream::Close() { started = false; return V4L2Stream::Close(); } class V4L2AutoQBUF { public: V4L2AutoQBUF(std::shared_ptr ctx, struct v4l2_buffer buf) : v4l2_ctx(ctx), v4l2_buf(buf) { } ~V4L2AutoQBUF() { if (v4l2_ctx->IoCtrl(VIDIOC_QBUF, &v4l2_buf) < 0) { LOG("index=%d, ioctl(VIDIOC_QBUF): %m\n", v4l2_buf.index); } } private: std::shared_ptr v4l2_ctx; struct v4l2_buffer v4l2_buf; }; class AutoQBUFMediaBuffer : public MediaBuffer { public: AutoQBUFMediaBuffer(const MediaBuffer& mb, std::shared_ptr ctx, struct v4l2_buffer buf) : MediaBuffer(mb), auto_qbuf(ctx, buf) { } private: V4L2AutoQBUF auto_qbuf; }; class AutoQBUFImageBuffer : public ImageBuffer { public: AutoQBUFImageBuffer(const MediaBuffer& mb, const ImageInfo& info, std::shared_ptr ctx, struct v4l2_buffer buf) : ImageBuffer(mb, info), auto_qbuf(ctx, buf) { } private: V4L2AutoQBUF auto_qbuf; }; std::shared_ptr V4L2CaptureStream::Read() { const char* dev = device.c_str(); if (!started && v4l2_ctx->SetStarted(true)) { started = true; } struct v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.type = capture_type; buf.memory = memory_type; int ret = v4l2_ctx->IoCtrl(VIDIOC_DQBUF, &buf); if (ret < 0) { LOG("%s, ioctl(VIDIOC_DQBUF): %m\n", dev); return nullptr; } struct timeval buf_ts = buf.timestamp; MediaBuffer& mb = buffer_vec[buf.index]; std::shared_ptr ret_buf; if (buf.bytesused > 0) { if (pix_fmt != PIX_FMT_NONE) { ImageInfo info{pix_fmt, width, height, width, height}; ret_buf = std::make_shared(mb, info, v4l2_ctx, buf); } else { ret_buf = std::make_shared(mb, v4l2_ctx, buf); } } if (ret_buf) { assert(ret_buf->GetFD() == mb.GetFD()); if (buf.memory == V4L2_MEMORY_DMABUF) { assert(ret_buf->GetFD() == buf.m.fd); } ret_buf->SetFrameSequenceNumber(buf.sequence); ret_buf->SetAtomicTimeVal(buf_ts); ret_buf->SetTimeVal(buf_ts); ret_buf->SetValidSize(buf.bytesused); } else { if (v4l2_ctx->IoCtrl(VIDIOC_QBUF, &buf) < 0) { LOG("%s, index=%d, ioctl(VIDIOC_QBUF): %m\n", dev, buf.index); } } return ret_buf; } DEFINE_STREAM_FACTORY(V4L2CaptureStream, Stream) const char* FACTORY(V4L2CaptureStream)::ExpectedInputDataType() { return nullptr; } const char* FACTORY(V4L2CaptureStream)::OutPutDataType() { return GetStringOfV4L2Fmts().c_str(); } } // namespace easymedia