/* * Copyright (C) 2018 Paul Kocialkowski * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "v4l2_req_dmabufs.h" #include "v4l2_req_media.h" #include "v4l2_req_pollqueue.h" #include "v4l2_req_utils.h" #include "weak_link.h" /* floor(log2(x)) */ static unsigned int log2_size(size_t x) { unsigned int n = 0; if (x & ~0xffff) { n += 16; x >>= 16; } if (x & ~0xff) { n += 8; x >>= 8; } if (x & ~0xf) { n += 4; x >>= 4; } if (x & ~3) { n += 2; x >>= 2; } return (x & ~1) ? n + 1 : n; } static size_t round_up_size(const size_t x) { /* Admit no size < 256 */ const unsigned int n = x < 256 ? 8 : log2_size(x) - 1; return x >= (3 << n) ? 4 << n : (3 << n); } struct media_request; struct media_pool { int fd; sem_t sem; pthread_mutex_t lock; unsigned int pool_n; struct media_request * pool_reqs; struct media_request * free_reqs; struct pollqueue * pq; }; struct media_request { struct media_request * next; struct media_pool * mp; int fd; struct polltask * pt; }; static inline enum v4l2_memory mediabufs_memory_to_v4l2(const enum mediabufs_memory m) { return (enum v4l2_memory)m; } const char * mediabufs_memory_name(const enum mediabufs_memory m) { switch (m) { case MEDIABUFS_MEMORY_UNSET: return "Unset"; case MEDIABUFS_MEMORY_MMAP: return "MMap"; case MEDIABUFS_MEMORY_USERPTR: return "UserPtr"; case MEDIABUFS_MEMORY_OVERLAY: return "Overlay"; case MEDIABUFS_MEMORY_DMABUF: return "DMABuf"; default: break; } return "Unknown"; } static inline int do_trywait(sem_t *const sem) { while (sem_trywait(sem)) { if (errno != EINTR) return -errno; } return 0; } static inline int do_wait(sem_t *const sem) { while (sem_wait(sem)) { if (errno != EINTR) return -errno; } return 0; } static int request_buffers(int video_fd, unsigned int type, enum mediabufs_memory memory, unsigned int buffers_count) { struct v4l2_requestbuffers buffers; int rc; memset(&buffers, 0, sizeof(buffers)); buffers.type = type; buffers.memory = mediabufs_memory_to_v4l2(memory); buffers.count = buffers_count; rc = ioctl(video_fd, VIDIOC_REQBUFS, &buffers); if (rc < 0) { rc = -errno; request_log("Unable to request %d type %d buffers: %s\n", buffers_count, type, strerror(-rc)); return rc; } return 0; } static int set_stream(int video_fd, unsigned int type, bool enable) { enum v4l2_buf_type buf_type = type; int rc; rc = ioctl(video_fd, enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF, &buf_type); if (rc < 0) { rc = -errno; request_log("Unable to %sable stream: %s\n", enable ? "en" : "dis", strerror(-rc)); return rc; } return 0; } struct media_request * media_request_get(struct media_pool * const mp) { struct media_request *req = NULL; /* Timeout handled by poll code */ if (do_wait(&mp->sem)) return NULL; pthread_mutex_lock(&mp->lock); req = mp->free_reqs; if (req) { mp->free_reqs = req->next; req->next = NULL; } pthread_mutex_unlock(&mp->lock); return req; } int media_request_fd(const struct media_request * const req) { return req->fd; } int media_request_start(struct media_request * const req) { while (ioctl(req->fd, MEDIA_REQUEST_IOC_QUEUE, NULL) == -1) { const int err = errno; if (err == EINTR) continue; request_log("%s: Failed to Q media: (%d) %s\n", __func__, err, strerror(err)); return -err; } pollqueue_add_task(req->pt, 2000); return 0; } static void media_request_done(void *v, short revents) { struct media_request *const req = v; struct media_pool *const mp = req->mp; /* ** Not sure what to do about timeout */ if (ioctl(req->fd, MEDIA_REQUEST_IOC_REINIT, NULL) < 0) request_log("Unable to reinit media request: %s\n", strerror(errno)); pthread_mutex_lock(&mp->lock); req->next = mp->free_reqs; mp->free_reqs = req; pthread_mutex_unlock(&mp->lock); sem_post(&mp->sem); } int media_request_abort(struct media_request ** const preq) { struct media_request * const req = *preq; if (req == NULL) return 0; *preq = NULL; media_request_done(req, 0); return 0; } static void free_req_pool(struct media_request * const pool, const unsigned int n) { unsigned int i; for (i = 0; i != n; ++i) { struct media_request * const req = pool + i; if (req->pt) polltask_delete(&req->pt); if (req->fd != -1) close(req->fd); } free(pool); } struct media_pool * media_pool_new(const char * const media_path, struct pollqueue * const pq, const unsigned int n) { struct media_pool * const mp = calloc(1, sizeof(*mp)); unsigned int i; if (!mp) goto fail0; mp->pq = pq; pthread_mutex_init(&mp->lock, NULL); mp->fd = open(media_path, O_RDWR | O_NONBLOCK); if (mp->fd == -1) { request_log("Failed to open '%s': %s\n", media_path, strerror(errno)); goto fail1; } if ((mp->pool_reqs = calloc(n, sizeof(*mp->pool_reqs))) == NULL) goto fail3; mp->pool_n = n; for (i = 0; i != n; ++i) { mp->pool_reqs[i].mp = mp; mp->pool_reqs[i].fd = -1; } for (i = 0; i != n; ++i) { struct media_request * const req = mp->pool_reqs + i; if (ioctl(mp->fd, MEDIA_IOC_REQUEST_ALLOC, &req->fd) == -1) { request_log("Failed to alloc request %d: %s\n", i, strerror(errno)); goto fail4; } req->pt = polltask_new(pq, req->fd, POLLPRI, media_request_done, req); if (!req->pt) goto fail4; req->next = mp->free_reqs, mp->free_reqs = req; } sem_init(&mp->sem, 0, n); return mp; fail4: free_req_pool(mp->pool_reqs, mp->pool_n); fail3: close(mp->fd); pthread_mutex_destroy(&mp->lock); fail1: free(mp); fail0: return NULL; } void media_pool_delete(struct media_pool ** pMp) { struct media_pool * const mp = *pMp; if (!mp) return; *pMp = NULL; free_req_pool(mp->pool_reqs, mp->pool_n); close(mp->fd); sem_destroy(&mp->sem); pthread_mutex_destroy(&mp->lock); free(mp); } #define INDEX_UNSET (~(uint32_t)0) enum qent_status { QENT_NEW = 0, // Initial state - shouldn't last QENT_FREE, // On free chain QENT_PENDING, // User has ent QENT_WAITING, // On inuse QENT_DONE, // Frame rx QENT_ERROR, // Error QENT_IMPORT }; struct qent_base { atomic_int ref_count; struct qent_base *next; struct qent_base *prev; enum qent_status status; enum mediabufs_memory memtype; uint32_t index; struct dmabuf_h *dh[VIDEO_MAX_PLANES]; struct timeval timestamp; }; struct qent_src { struct qent_base base; int fixed_size; }; struct qent_dst { struct qent_base base; bool waiting; pthread_mutex_t lock; pthread_cond_t cond; struct ff_weak_link_client * mbc_wl; }; struct qe_list_head { struct qent_base *head; struct qent_base *tail; }; struct buf_pool { enum mediabufs_memory memtype; pthread_mutex_t lock; pthread_cond_t cond; struct qe_list_head free; struct qe_list_head inuse; }; static inline struct qent_dst *base_to_dst(struct qent_base *be) { return (struct qent_dst *)be; } static inline struct qent_src *base_to_src(struct qent_base *be) { return (struct qent_src *)be; } #define QENT_BASE_INITIALIZER(mtype) {\ .ref_count = ATOMIC_VAR_INIT(0),\ .status = QENT_NEW,\ .memtype = (mtype),\ .index = INDEX_UNSET\ } static void qe_base_uninit(struct qent_base *const be) { unsigned int i; for (i = 0; i != VIDEO_MAX_PLANES; ++i) { dmabuf_free(be->dh[i]); be->dh[i] = NULL; } } static void qe_src_free(struct qent_src *const be_src) { if (!be_src) return; qe_base_uninit(&be_src->base); free(be_src); } static struct qent_src * qe_src_new(enum mediabufs_memory mtype) { struct qent_src *const be_src = malloc(sizeof(*be_src)); if (!be_src) return NULL; *be_src = (struct qent_src){ .base = QENT_BASE_INITIALIZER(mtype) }; return be_src; } static void qe_dst_free(struct qent_dst *const be_dst) { if (!be_dst) return; ff_weak_link_unref(&be_dst->mbc_wl); pthread_cond_destroy(&be_dst->cond); pthread_mutex_destroy(&be_dst->lock); qe_base_uninit(&be_dst->base); free(be_dst); } static struct qent_dst* qe_dst_new(struct ff_weak_link_master * const wl, const enum mediabufs_memory memtype) { struct qent_dst *const be_dst = malloc(sizeof(*be_dst)); if (!be_dst) return NULL; *be_dst = (struct qent_dst){ .base = QENT_BASE_INITIALIZER(memtype), .lock = PTHREAD_MUTEX_INITIALIZER, .cond = PTHREAD_COND_INITIALIZER, .mbc_wl = ff_weak_link_ref(wl) }; return be_dst; } static void ql_add_tail(struct qe_list_head * const ql, struct qent_base * be) { if (ql->tail) ql->tail->next = be; else ql->head = be; be->prev = ql->tail; be->next = NULL; ql->tail = be; } static struct qent_base * ql_extract(struct qe_list_head * const ql, struct qent_base * be) { if (!be) return NULL; if (be->next) be->next->prev = be->prev; else ql->tail = be->prev; if (be->prev) be->prev->next = be->next; else ql->head = be->next; be->next = NULL; be->prev = NULL; return be; } static void bq_put_free(struct buf_pool *const bp, struct qent_base * be) { ql_add_tail(&bp->free, be); } static struct qent_base * bq_get_free(struct buf_pool *const bp) { return ql_extract(&bp->free, bp->free.head); } static struct qent_base * bq_extract_inuse(struct buf_pool *const bp, struct qent_base *const be) { return ql_extract(&bp->inuse, be); } static struct qent_base * bq_get_inuse(struct buf_pool *const bp) { return ql_extract(&bp->inuse, bp->inuse.head); } static void bq_free_all_free_src(struct buf_pool *const bp) { struct qent_base *be; while ((be = bq_get_free(bp)) != NULL) qe_src_free(base_to_src(be)); } static void bq_free_all_inuse_src(struct buf_pool *const bp) { struct qent_base *be; while ((be = bq_get_inuse(bp)) != NULL) qe_src_free(base_to_src(be)); } static void bq_free_all_free_dst(struct buf_pool *const bp) { struct qent_base *be; while ((be = bq_get_free(bp)) != NULL) qe_dst_free(base_to_dst(be)); } static void queue_put_free(struct buf_pool *const bp, struct qent_base *be) { unsigned int i; pthread_mutex_lock(&bp->lock); /* Clear out state vars */ be->timestamp.tv_sec = 0; be->timestamp.tv_usec = 0; be->status = QENT_FREE; for (i = 0; i < VIDEO_MAX_PLANES && be->dh[i]; ++i) dmabuf_len_set(be->dh[i], 0); bq_put_free(bp, be); pthread_cond_broadcast(&bp->cond); pthread_mutex_unlock(&bp->lock); } static bool queue_is_inuse(const struct buf_pool *const bp) { return bp->inuse.tail != NULL; } static void queue_put_inuse(struct buf_pool *const bp, struct qent_base *be) { if (!be) return; pthread_mutex_lock(&bp->lock); ql_add_tail(&bp->inuse, be); be->status = QENT_WAITING; pthread_mutex_unlock(&bp->lock); } static struct qent_base *queue_get_free(struct buf_pool *const bp) { struct qent_base *buf; pthread_mutex_lock(&bp->lock); while ((buf = bq_get_free(bp)) == NULL && pthread_cond_wait(&bp->cond, &bp->lock) == 0) /* Loop */; pthread_mutex_unlock(&bp->lock); return buf; } static struct qent_base *queue_tryget_free(struct buf_pool *const bp) { struct qent_base *buf; pthread_mutex_lock(&bp->lock); buf = bq_get_free(bp); pthread_mutex_unlock(&bp->lock); return buf; } static struct qent_base * queue_find_extract_index(struct buf_pool *const bp, const unsigned int index) { struct qent_base *be; pthread_mutex_lock(&bp->lock); /* Expect 1st in Q, but allow anywhere */ for (be = bp->inuse.head; be; be = be->next) { if (be->index == index) { bq_extract_inuse(bp, be); break; } } pthread_mutex_unlock(&bp->lock); return be; } static void queue_delete(struct buf_pool *const bp) { pthread_cond_destroy(&bp->cond); pthread_mutex_destroy(&bp->lock); free(bp); } static struct buf_pool* queue_new(const int vfd) { struct buf_pool *bp = calloc(1, sizeof(*bp)); if (!bp) return NULL; pthread_mutex_init(&bp->lock, NULL); pthread_cond_init(&bp->cond, NULL); return bp; } struct mediabufs_ctl { atomic_int ref_count; /* 0 is single ref for easier atomics */ void * dc; int vfd; bool stream_on; bool polling; bool dst_fixed; // Dst Q is fixed size pthread_mutex_t lock; struct buf_pool * src; struct buf_pool * dst; struct polltask * pt; struct pollqueue * pq; struct ff_weak_link_master * this_wlm; enum mediabufs_memory src_memtype; enum mediabufs_memory dst_memtype; struct v4l2_format src_fmt; struct v4l2_format dst_fmt; struct v4l2_capability capability; }; static int qe_v4l2_queue(struct qent_base *const be, const int vfd, struct media_request *const mreq, const struct v4l2_format *const fmt, const bool is_dst, const bool hold_flag) { struct v4l2_buffer buffer = { .type = fmt->type, .memory = mediabufs_memory_to_v4l2(be->memtype), .index = be->index }; struct v4l2_plane planes[VIDEO_MAX_PLANES] = {{0}}; if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { unsigned int i; for (i = 0; i < VIDEO_MAX_PLANES && be->dh[i]; ++i) { if (is_dst) dmabuf_len_set(be->dh[i], 0); /* *** Really need a pixdesc rather than a format so we can fill in data_offset */ planes[i].length = dmabuf_size(be->dh[i]); planes[i].bytesused = dmabuf_len(be->dh[i]); if (be->memtype == MEDIABUFS_MEMORY_DMABUF) planes[i].m.fd = dmabuf_fd(be->dh[i]); else planes[i].m.mem_offset = 0; } buffer.m.planes = planes; buffer.length = i; } else { if (is_dst) dmabuf_len_set(be->dh[0], 0); buffer.bytesused = dmabuf_len(be->dh[0]); buffer.length = dmabuf_size(be->dh[0]); if (be->memtype == MEDIABUFS_MEMORY_DMABUF) buffer.m.fd = dmabuf_fd(be->dh[0]); else buffer.m.offset = 0; } if (!is_dst && mreq) { buffer.flags |= V4L2_BUF_FLAG_REQUEST_FD; buffer.request_fd = media_request_fd(mreq); if (hold_flag) buffer.flags |= V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF; } if (is_dst) be->timestamp = (struct timeval){0,0}; buffer.timestamp = be->timestamp; while (ioctl(vfd, VIDIOC_QBUF, &buffer)) { const int err = errno; if (err != EINTR) { request_log("%s: Failed to Q buffer: err=%d (%s)\n", __func__, err, strerror(err)); return -err; } } return 0; } static struct qent_base * qe_dequeue(struct buf_pool *const bp, const int vfd, const struct v4l2_format * const f) { struct qent_base *be; int rc; const bool mp = V4L2_TYPE_IS_MULTIPLANAR(f->type); struct v4l2_plane planes[VIDEO_MAX_PLANES] = {{0}}; struct v4l2_buffer buffer = { .type = f->type, .memory = mediabufs_memory_to_v4l2(bp->memtype) }; if (mp) { buffer.length = f->fmt.pix_mp.num_planes; buffer.m.planes = planes; } while ((rc = ioctl(vfd, VIDIOC_DQBUF, &buffer)) != 0 && errno == EINTR) /* Loop */; if (rc) { request_log("Error DQing buffer type %d: %s\n", f->type, strerror(errno)); return NULL; } be = queue_find_extract_index(bp, buffer.index); if (!be) { request_log("Failed to find index %d in Q\n", buffer.index); return NULL; } if (mp) { unsigned int i; for (i = 0; i != buffer.length; ++i) dmabuf_len_set(be->dh[i], V4L2_TYPE_IS_CAPTURE(f->type) ? planes[i].bytesused : 0); } else dmabuf_len_set(be->dh[0], V4L2_TYPE_IS_CAPTURE(f->type) ? buffer.length : 0); be->timestamp = buffer.timestamp; be->status = (buffer.flags & V4L2_BUF_FLAG_ERROR) ? QENT_ERROR : QENT_DONE; return be; } static void qe_dst_done(struct qent_dst * dst_be) { pthread_mutex_lock(&dst_be->lock); dst_be->waiting = false; pthread_cond_broadcast(&dst_be->cond); pthread_mutex_unlock(&dst_be->lock); qent_dst_unref(&dst_be); } static bool qe_dst_waiting(struct qent_dst *const dst_be) { bool waiting; pthread_mutex_lock(&dst_be->lock); waiting = dst_be->waiting; dst_be->waiting = true; pthread_mutex_unlock(&dst_be->lock); return waiting; } static bool mediabufs_wants_poll(const struct mediabufs_ctl *const mbc) { return queue_is_inuse(mbc->src) || queue_is_inuse(mbc->dst); } static void mediabufs_poll_cb(void * v, short revents) { struct mediabufs_ctl *mbc = v; struct qent_src *src_be = NULL; struct qent_dst *dst_be = NULL; if (!revents) request_err(mbc->dc, "%s: Timeout\n", __func__); pthread_mutex_lock(&mbc->lock); mbc->polling = false; if ((revents & POLLOUT) != 0) src_be = base_to_src(qe_dequeue(mbc->src, mbc->vfd, &mbc->src_fmt)); if ((revents & POLLIN) != 0) dst_be = base_to_dst(qe_dequeue(mbc->dst, mbc->vfd, &mbc->dst_fmt)); /* Reschedule */ if (mediabufs_wants_poll(mbc)) { mbc->polling = true; pollqueue_add_task(mbc->pt, 2000); } pthread_mutex_unlock(&mbc->lock); if (src_be) queue_put_free(mbc->src, &src_be->base); if (dst_be) qe_dst_done(dst_be); } int qent_src_params_set(struct qent_src *const be_src, const struct timeval * timestamp) { struct qent_base *const be = &be_src->base; be->timestamp = *timestamp; return 0; } struct timeval qent_dst_timestamp_get(const struct qent_dst *const be_dst) { return be_dst->base.timestamp; } static int qent_base_realloc(struct qent_base *const be, const size_t len, struct dmabufs_ctl * dbsc) { if (!be->dh[0] || len > dmabuf_size(be->dh[0])) { size_t newsize = round_up_size(len); request_debug(NULL, "%s: Overrun %zd > %zd; trying %zd\n", __func__, len, dmabuf_size(be->dh[0]), newsize); if (!dbsc) { request_log("%s: No dmbabuf_ctrl for realloc\n", __func__); return -ENOMEM; } if ((be->dh[0] = dmabuf_realloc(dbsc, be->dh[0], newsize)) == NULL) { request_log("%s: Realloc %zd failed\n", __func__, newsize); return -ENOMEM; } } return 0; } int qent_src_alloc(struct qent_src *const be_src, const size_t len, struct dmabufs_ctl * dbsc) { struct qent_base *const be = &be_src->base; return qent_base_realloc(be, len, dbsc); } int qent_src_data_copy(struct qent_src *const be_src, const size_t offset, const void *const src, const size_t len, struct dmabufs_ctl * dbsc) { void * dst; struct qent_base *const be = &be_src->base; int rv; // Realloc doesn't copy so don't alloc if offset != 0 if ((rv = qent_base_realloc(be, offset + len, be_src->fixed_size || offset ? NULL : dbsc)) != 0) return rv; dmabuf_write_start(be->dh[0]); dst = dmabuf_map(be->dh[0]); if (!dst) return -1; memcpy((char*)dst + offset, src, len); dmabuf_len_set(be->dh[0], len); dmabuf_write_end(be->dh[0]); return 0; } const struct dmabuf_h * qent_dst_dmabuf(const struct qent_dst *const be_dst, unsigned int plane) { const struct qent_base *const be = &be_dst->base; return (plane >= sizeof(be->dh)/sizeof(be->dh[0])) ? NULL : be->dh[plane]; } int qent_dst_dup_fd(const struct qent_dst *const be_dst, unsigned int plane) { return dup(dmabuf_fd(qent_dst_dmabuf(be_dst, plane))); } MediaBufsStatus mediabufs_start_request(struct mediabufs_ctl *const mbc, struct media_request **const pmreq, struct qent_src **const psrc_be, struct qent_dst *const dst_be, const bool is_final) { struct media_request * mreq = *pmreq; struct qent_src *const src_be = *psrc_be; // Req & src are always both "consumed" *pmreq = NULL; *psrc_be = NULL; pthread_mutex_lock(&mbc->lock); if (!src_be) goto fail1; if (dst_be) { if (qe_dst_waiting(dst_be)) { request_info(mbc->dc, "Request buffer already waiting on start\n"); goto fail1; } dst_be->base.timestamp = (struct timeval){0,0}; if (qe_v4l2_queue(&dst_be->base, mbc->vfd, NULL, &mbc->dst_fmt, true, false)) goto fail1; qent_dst_ref(dst_be); queue_put_inuse(mbc->dst, &dst_be->base); } if (qe_v4l2_queue(&src_be->base, mbc->vfd, mreq, &mbc->src_fmt, false, !is_final)) goto fail1; queue_put_inuse(mbc->src, &src_be->base); if (!mbc->polling && mediabufs_wants_poll(mbc)) { mbc->polling = true; pollqueue_add_task(mbc->pt, 2000); } pthread_mutex_unlock(&mbc->lock); if (media_request_start(mreq)) return MEDIABUFS_ERROR_OPERATION_FAILED; return MEDIABUFS_STATUS_SUCCESS; fail1: media_request_abort(&mreq); if (src_be) queue_put_free(mbc->src, &src_be->base); // *** TODO: If src Q fails this doesnt unwind properly - separate dst Q from src Q if (dst_be) { dst_be->base.status = QENT_ERROR; qe_dst_done(dst_be); } pthread_mutex_unlock(&mbc->lock); return MEDIABUFS_ERROR_OPERATION_FAILED; } static int qe_alloc_from_fmt(struct qent_base *const be, struct dmabufs_ctl *const dbsc, const struct v4l2_format *const fmt) { if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { unsigned int i; for (i = 0; i != fmt->fmt.pix_mp.num_planes; ++i) { be->dh[i] = dmabuf_realloc(dbsc, be->dh[i], fmt->fmt.pix_mp.plane_fmt[i].sizeimage); /* On failure tidy up and die */ if (!be->dh[i]) { while (i--) { dmabuf_free(be->dh[i]); be->dh[i] = NULL; } return -1; } } } else { // be->dh[0] = dmabuf_alloc(dbsc, fmt->fmt.pix.sizeimage); size_t size = fmt->fmt.pix.sizeimage; be->dh[0] = dmabuf_realloc(dbsc, be->dh[0], size); if (!be->dh[0]) return -1; } return 0; } static MediaBufsStatus fmt_set(struct v4l2_format *const fmt, const int fd, const enum v4l2_buf_type buftype, uint32_t pixfmt, const unsigned int width, const unsigned int height, const size_t bufsize) { *fmt = (struct v4l2_format){.type = buftype}; if (V4L2_TYPE_IS_MULTIPLANAR(buftype)) { fmt->fmt.pix_mp.width = width; fmt->fmt.pix_mp.height = height; fmt->fmt.pix_mp.pixelformat = pixfmt; if (bufsize) { fmt->fmt.pix_mp.num_planes = 1; fmt->fmt.pix_mp.plane_fmt[0].sizeimage = bufsize; } } else { fmt->fmt.pix.width = width; fmt->fmt.pix.height = height; fmt->fmt.pix.pixelformat = pixfmt; fmt->fmt.pix.sizeimage = bufsize; } while (ioctl(fd, VIDIOC_S_FMT, fmt)) if (errno != EINTR) return MEDIABUFS_ERROR_OPERATION_FAILED; // Treat anything where we don't get at least what we asked for as a fail if (V4L2_TYPE_IS_MULTIPLANAR(buftype)) { if (fmt->fmt.pix_mp.width < width || fmt->fmt.pix_mp.height < height || fmt->fmt.pix_mp.pixelformat != pixfmt) { return MEDIABUFS_ERROR_UNSUPPORTED_BUFFERTYPE; } } else { if (fmt->fmt.pix.width < width || fmt->fmt.pix.height < height || fmt->fmt.pix.pixelformat != pixfmt) { return MEDIABUFS_ERROR_UNSUPPORTED_BUFFERTYPE; } } return MEDIABUFS_STATUS_SUCCESS; } static MediaBufsStatus find_fmt_flags(struct v4l2_format *const fmt, const int fd, const unsigned int type_v4l2, const uint32_t flags_must, const uint32_t flags_not, const unsigned int width, const unsigned int height, mediabufs_dst_fmt_accept_fn *const accept_fn, void *const accept_v) { unsigned int i; for (i = 0;; ++i) { struct v4l2_fmtdesc fmtdesc = { .index = i, .type = type_v4l2 }; while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) { if (errno != EINTR) return MEDIABUFS_ERROR_UNSUPPORTED_BUFFERTYPE; } if ((fmtdesc.flags & flags_must) != flags_must || (fmtdesc.flags & flags_not)) continue; if (!accept_fn(accept_v, &fmtdesc)) continue; if (fmt_set(fmt, fd, fmtdesc.type, fmtdesc.pixelformat, width, height, 0) == MEDIABUFS_STATUS_SUCCESS) return MEDIABUFS_STATUS_SUCCESS; } return 0; } /* Wait for qent done */ MediaBufsStatus qent_dst_wait(struct qent_dst *const be_dst) { struct qent_base *const be = &be_dst->base; enum qent_status estat; pthread_mutex_lock(&be_dst->lock); while (be_dst->waiting && !pthread_cond_wait(&be_dst->cond, &be_dst->lock)) /* Loop */; estat = be->status; pthread_mutex_unlock(&be_dst->lock); return estat == QENT_DONE ? MEDIABUFS_STATUS_SUCCESS : estat == QENT_ERROR ? MEDIABUFS_ERROR_DECODING_ERROR : MEDIABUFS_ERROR_OPERATION_FAILED; } const uint8_t * qent_dst_data(struct qent_dst *const be_dst, unsigned int buf_no) { struct qent_base *const be = &be_dst->base; return dmabuf_map(be->dh[buf_no]); } MediaBufsStatus qent_dst_read_start(struct qent_dst *const be_dst) { struct qent_base *const be = &be_dst->base; unsigned int i; for (i = 0; i != VIDEO_MAX_PLANES && be->dh[i]; ++i) { if (dmabuf_read_start(be->dh[i])) { while (i--) dmabuf_read_end(be->dh[i]); return MEDIABUFS_ERROR_ALLOCATION_FAILED; } } return MEDIABUFS_STATUS_SUCCESS; } MediaBufsStatus qent_dst_read_stop(struct qent_dst *const be_dst) { struct qent_base *const be = &be_dst->base; unsigned int i; MediaBufsStatus status = MEDIABUFS_STATUS_SUCCESS; for (i = 0; i != VIDEO_MAX_PLANES && be->dh[i]; ++i) { if (dmabuf_read_end(be->dh[i])) status = MEDIABUFS_ERROR_OPERATION_FAILED; } return status; } struct qent_dst * qent_dst_ref(struct qent_dst * const be_dst) { if (be_dst) atomic_fetch_add(&be_dst->base.ref_count, 1); return be_dst; } void qent_dst_unref(struct qent_dst ** const pbe_dst) { struct qent_dst * const be_dst = *pbe_dst; struct mediabufs_ctl * mbc; if (!be_dst) return; *pbe_dst = NULL; if (atomic_fetch_sub(&be_dst->base.ref_count, 1) != 0) return; if ((mbc = ff_weak_link_lock(&be_dst->mbc_wl)) != NULL) { queue_put_free(mbc->dst, &be_dst->base); ff_weak_link_unlock(be_dst->mbc_wl); } else { qe_dst_free(be_dst); } } MediaBufsStatus qent_dst_import_fd(struct qent_dst *const be_dst, unsigned int plane, int fd, size_t size) { struct qent_base *const be = &be_dst->base; struct dmabuf_h * dh; if (be->status != QENT_IMPORT || be->dh[plane]) return MEDIABUFS_ERROR_OPERATION_FAILED; dh = dmabuf_import(fd, size); if (!dh) return MEDIABUFS_ERROR_ALLOCATION_FAILED; be->dh[plane] = dh; return MEDIABUFS_STATUS_SUCCESS; } // Returns noof buffers created, -ve for error static int create_dst_bufs(struct mediabufs_ctl *const mbc, unsigned int n, struct qent_dst * const qes[]) { unsigned int i; struct v4l2_create_buffers cbuf = { .count = n, .memory = mediabufs_memory_to_v4l2(mbc->dst->memtype), .format = mbc->dst_fmt, }; while (ioctl(mbc->vfd, VIDIOC_CREATE_BUFS, &cbuf)) { const int err = -errno; if (err != EINTR) { request_err(mbc->dc, "%s: Failed to create V4L2 buffer\n", __func__); return -err; } } if (cbuf.count != n) request_warn(mbc->dc, "%s: Created %d of %d V4L2 buffers requested\n", __func__, cbuf.count, n); for (i = 0; i != cbuf.count; ++i) qes[i]->base.index = cbuf.index + i; return cbuf.count; } static void queue_dst_wait_no_waiting(struct buf_pool *const bp) { // This is O(n^2), but n is not very big & shoudl only happen on shutdown // so it isn't worth adding any processing to anything else to make this // more efficient for (;;) { struct qent_base *be; struct qent_dst *be_dst = NULL; pthread_mutex_lock(&bp->lock); for (be = bp->inuse.head; be != NULL; be = be->next) { be_dst = base_to_dst(be); if (be_dst->waiting) { qent_dst_ref(be_dst); break; } } pthread_mutex_unlock(&bp->lock); if (be == NULL) break; qent_dst_wait(be_dst); } } static MediaBufsStatus qe_import_from_buf(struct mediabufs_ctl *const mbc, struct qent_base * const be, const struct v4l2_format *const fmt, const unsigned int n, const bool x_dmabuf) { struct v4l2_buffer buf = { .index = n, .type = fmt->type, }; struct v4l2_plane planes[VIDEO_MAX_PLANES]; int ret; if (be->dh[0]) return 0; if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { memset(planes, 0, sizeof(planes)); buf.m.planes = planes; buf.length = VIDEO_MAX_PLANES; } if ((ret = ioctl(mbc->vfd, VIDIOC_QUERYBUF, &buf)) != 0) { request_err(mbc->dc, "VIDIOC_QUERYBUF failed"); return MEDIABUFS_ERROR_OPERATION_FAILED; } if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { unsigned int i; for (i = 0; i != buf.length; ++i) { if (x_dmabuf) { struct v4l2_exportbuffer xbuf = { .type = buf.type, .index = buf.index, .plane = i, .flags = O_RDWR, // *** Arguably O_RDONLY would be fine }; if (ioctl(mbc->vfd, VIDIOC_EXPBUF, &xbuf) == 0) { be->dh[i] = dmabuf_import(xbuf.fd, planes[i].length); close(xbuf.fd); // dmabuf_import dups the fd so close this one } } else { be->dh[i] = dmabuf_import_mmap( mmap(NULL, planes[i].length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, mbc->vfd, planes[i].m.mem_offset), planes[i].length); } /* On failure tidy up and die */ if (!be->dh[i]) { while (i--) { dmabuf_free(be->dh[i]); be->dh[i] = NULL; } return MEDIABUFS_ERROR_OPERATION_FAILED; } } } else { if (x_dmabuf) { struct v4l2_exportbuffer xbuf = { .type = buf.type, .index = buf.index, .flags = O_RDWR, // *** Arguably O_RDONLY would be fine }; if (ioctl(mbc->vfd, VIDIOC_EXPBUF, &xbuf) == 0) be->dh[0] = dmabuf_import(xbuf.fd, buf.length); } else { be->dh[0] = dmabuf_import_mmap( mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, mbc->vfd, buf.m.offset), buf.length); } /* On failure tidy up and die */ if (!be->dh[0]) { return MEDIABUFS_ERROR_OPERATION_FAILED; } } return 0; } struct qent_dst* mediabufs_dst_qent_alloc(struct mediabufs_ctl *const mbc, struct dmabufs_ctl *const dbsc) { struct qent_dst * be_dst; if (mbc == NULL) { be_dst = qe_dst_new(NULL, MEDIABUFS_MEMORY_DMABUF); if (be_dst) be_dst->base.status = QENT_IMPORT; return be_dst; } if (mbc->dst_fixed) { be_dst = base_to_dst(queue_get_free(mbc->dst)); if (!be_dst) return NULL; } else { be_dst = base_to_dst(queue_tryget_free(mbc->dst)); if (!be_dst) { be_dst = qe_dst_new(mbc->this_wlm, mbc->dst->memtype); if (!be_dst) return NULL; if (create_dst_bufs(mbc, 1, &be_dst) != 1) { qe_dst_free(be_dst); // We can't extend any more - try again but wait mbc->dst_fixed = true; return mediabufs_dst_qent_alloc(mbc, dbsc); } } } if (mbc->dst->memtype == MEDIABUFS_MEMORY_MMAP) { if (qe_import_from_buf(mbc, &be_dst->base, &mbc->dst_fmt, be_dst->base.index, true)) { request_err(mbc->dc, "Failed to export as dmabuf\n"); queue_put_free(mbc->dst, &be_dst->base); return NULL; } } else { if (qe_alloc_from_fmt(&be_dst->base, dbsc, &mbc->dst_fmt)) { /* Given how create buf works we can't uncreate it on alloc failure * all we can do is put it on the free Q */ queue_put_free(mbc->dst, &be_dst->base); return NULL; } } be_dst->base.status = QENT_PENDING; atomic_store(&be_dst->base.ref_count, 0); return be_dst; } const struct v4l2_format *mediabufs_dst_fmt(struct mediabufs_ctl *const mbc) { return &mbc->dst_fmt; } MediaBufsStatus mediabufs_dst_fmt_set(struct mediabufs_ctl *const mbc, const unsigned int width, const unsigned int height, mediabufs_dst_fmt_accept_fn *const accept_fn, void *const accept_v) { MediaBufsStatus status; unsigned int i; const enum v4l2_buf_type buf_type = mbc->dst_fmt.type; static const struct { unsigned int flags_must; unsigned int flags_not; } trys[] = { {0, V4L2_FMT_FLAG_EMULATED}, {V4L2_FMT_FLAG_EMULATED, 0}, }; for (i = 0; i != sizeof(trys)/sizeof(trys[0]); ++i) { status = find_fmt_flags(&mbc->dst_fmt, mbc->vfd, buf_type, trys[i].flags_must, trys[i].flags_not, width, height, accept_fn, accept_v); if (status != MEDIABUFS_ERROR_UNSUPPORTED_BUFFERTYPE) return status; } if (status != MEDIABUFS_STATUS_SUCCESS) return status; /* Try to create a buffer - don't alloc */ return status; } // ** This is a mess if we get partial alloc but without any way to remove // individual V4L2 Q members we are somewhat stuffed MediaBufsStatus mediabufs_dst_slots_create(struct mediabufs_ctl *const mbc, const unsigned int n, const bool fixed, const enum mediabufs_memory memtype) { unsigned int i; int a = 0; unsigned int qc; struct qent_dst * qes[32]; if (n > 32) return MEDIABUFS_ERROR_ALLOCATION_FAILED; mbc->dst->memtype = memtype; // Create qents first as it is hard to get rid of the V4L2 buffers on error for (qc = 0; qc != n; ++qc) { if ((qes[qc] = qe_dst_new(mbc->this_wlm, mbc->dst->memtype)) == NULL) goto fail; } if ((a = create_dst_bufs(mbc, n, qes)) < 0) goto fail; for (i = 0; i != a; ++i) queue_put_free(mbc->dst, &qes[i]->base); if (a != n) goto fail; mbc->dst_fixed = fixed; return MEDIABUFS_STATUS_SUCCESS; fail: for (i = (a < 0 ? 0 : a); i != qc; ++i) qe_dst_free(qes[i]); return MEDIABUFS_ERROR_ALLOCATION_FAILED; } struct qent_src *mediabufs_src_qent_get(struct mediabufs_ctl *const mbc) { struct qent_base * buf = queue_get_free(mbc->src); buf->status = QENT_PENDING; return base_to_src(buf); } void mediabufs_src_qent_abort(struct mediabufs_ctl *const mbc, struct qent_src **const pqe_src) { struct qent_src *const qe_src = *pqe_src; if (!qe_src) return; *pqe_src = NULL; queue_put_free(mbc->src, &qe_src->base); } static MediaBufsStatus chk_memory_type(struct mediabufs_ctl *const mbc, const struct v4l2_format * const f, const enum mediabufs_memory m) { struct v4l2_create_buffers cbuf = { .count = 0, .memory = V4L2_MEMORY_MMAP, .format = *f }; if (ioctl(mbc->vfd, VIDIOC_CREATE_BUFS, &cbuf) != 0) return MEDIABUFS_ERROR_OPERATION_FAILED; switch (m) { case MEDIABUFS_MEMORY_DMABUF: // 0 = Unknown but assume not in that case if ((cbuf.capabilities & V4L2_BUF_CAP_SUPPORTS_DMABUF) == 0) return MEDIABUFS_ERROR_UNSUPPORTED_MEMORY; break; case MEDIABUFS_MEMORY_MMAP: break; default: return MEDIABUFS_ERROR_UNSUPPORTED_MEMORY; } return MEDIABUFS_STATUS_SUCCESS; } MediaBufsStatus mediabufs_src_chk_memtype(struct mediabufs_ctl *const mbc, const enum mediabufs_memory memtype) { return chk_memory_type(mbc, &mbc->src_fmt, memtype); } MediaBufsStatus mediabufs_dst_chk_memtype(struct mediabufs_ctl *const mbc, const enum mediabufs_memory memtype) { return chk_memory_type(mbc, &mbc->dst_fmt, memtype); } /* src format must have been set up before this */ MediaBufsStatus mediabufs_src_pool_create(struct mediabufs_ctl *const mbc, struct dmabufs_ctl * const dbsc, unsigned int n, const enum mediabufs_memory memtype) { unsigned int i; struct v4l2_requestbuffers req = { .count = n, .type = mbc->src_fmt.type, .memory = mediabufs_memory_to_v4l2(memtype) }; bq_free_all_free_src(mbc->src); while (ioctl(mbc->vfd, VIDIOC_REQBUFS, &req) == -1) { if (errno != EINTR) { request_err(mbc->dc, "%s: Failed to request src bufs\n", __func__); return MEDIABUFS_ERROR_OPERATION_FAILED; } } if (n > req.count) { request_info(mbc->dc, "Only allocated %d of %d src buffers requested\n", req.count, n); n = req.count; } for (i = 0; i != n; ++i) { struct qent_src *const be_src = qe_src_new(memtype); if (!be_src) { request_err(mbc->dc, "Failed to create src be %d\n", i); goto fail; } switch (memtype) { case MEDIABUFS_MEMORY_MMAP: if (qe_import_from_buf(mbc, &be_src->base, &mbc->src_fmt, i, false)) { qe_src_free(be_src); goto fail; } be_src->fixed_size = 1; break; case MEDIABUFS_MEMORY_DMABUF: if (qe_alloc_from_fmt(&be_src->base, dbsc, &mbc->src_fmt)) { qe_src_free(be_src); goto fail; } be_src->fixed_size = !mediabufs_src_resizable(mbc); break; default: request_err(mbc->dc, "Unexpected memorty type\n"); goto fail; } be_src->base.index = i; queue_put_free(mbc->src, &be_src->base); } mbc->src->memtype = memtype; return MEDIABUFS_STATUS_SUCCESS; fail: bq_free_all_free_src(mbc->src); req.count = 0; while (ioctl(mbc->vfd, VIDIOC_REQBUFS, &req) == -1 && errno == EINTR) /* Loop */; return MEDIABUFS_ERROR_OPERATION_FAILED; } /* * Set stuff order: * Set src fmt * Set parameters (sps) on vfd * Negotiate dst format (dst_fmt_set) * Create src buffers * Alloc a dst buffer or Create dst slots */ MediaBufsStatus mediabufs_stream_on(struct mediabufs_ctl *const mbc) { if (mbc->stream_on) return MEDIABUFS_STATUS_SUCCESS; if (set_stream(mbc->vfd, mbc->src_fmt.type, true) < 0) { request_log("Failed to set stream on src type %d\n", mbc->src_fmt.type); return MEDIABUFS_ERROR_OPERATION_FAILED; } if (set_stream(mbc->vfd, mbc->dst_fmt.type, true) < 0) { request_log("Failed to set stream on dst type %d\n", mbc->dst_fmt.type); set_stream(mbc->vfd, mbc->src_fmt.type, false); return MEDIABUFS_ERROR_OPERATION_FAILED; } mbc->stream_on = true; return MEDIABUFS_STATUS_SUCCESS; } MediaBufsStatus mediabufs_stream_off(struct mediabufs_ctl *const mbc) { MediaBufsStatus status = MEDIABUFS_STATUS_SUCCESS; if (!mbc->stream_on) return MEDIABUFS_STATUS_SUCCESS; if (set_stream(mbc->vfd, mbc->dst_fmt.type, false) < 0) { request_log("Failed to set stream off dst type %d\n", mbc->dst_fmt.type); status = MEDIABUFS_ERROR_OPERATION_FAILED; } if (set_stream(mbc->vfd, mbc->src_fmt.type, false) < 0) { request_log("Failed to set stream off src type %d\n", mbc->src_fmt.type); status = MEDIABUFS_ERROR_OPERATION_FAILED; } mbc->stream_on = false; return status; } MediaBufsStatus mediabufs_stream_wait_dst_done(struct mediabufs_ctl *const mbc) { queue_dst_wait_no_waiting(mbc->dst); return MEDIABUFS_STATUS_SUCCESS; } int mediabufs_ctl_set_ext_ctrls(struct mediabufs_ctl * mbc, struct media_request * const mreq, struct v4l2_ext_control * const control_array, unsigned int n) { struct v4l2_ext_controls controls = { .controls = control_array, .count = n }; if (mreq) { controls.which = V4L2_CTRL_WHICH_REQUEST_VAL; controls.request_fd = media_request_fd(mreq); } while (ioctl(mbc->vfd, VIDIOC_S_EXT_CTRLS, &controls)) { const int err = errno; if (err != EINTR) { request_err(mbc->dc, "Unable to set controls: %s\n", strerror(err)); return -err; } } return 0; } MediaBufsStatus mediabufs_set_ext_ctrl(struct mediabufs_ctl *const mbc, struct media_request * const mreq, unsigned int id, void *data, unsigned int size) { struct v4l2_ext_control control = { .id = id, .ptr = data, .size = size }; int rv = mediabufs_ctl_set_ext_ctrls(mbc, mreq, &control, 1); return !rv ? MEDIABUFS_STATUS_SUCCESS : MEDIABUFS_ERROR_OPERATION_FAILED; } MediaBufsStatus mediabufs_src_fmt_set(struct mediabufs_ctl *const mbc, enum v4l2_buf_type buf_type, const uint32_t pixfmt, const uint32_t width, const uint32_t height, const size_t bufsize) { MediaBufsStatus rv = fmt_set(&mbc->src_fmt, mbc->vfd, buf_type, pixfmt, width, height, bufsize); if (rv != MEDIABUFS_STATUS_SUCCESS) request_err(mbc->dc, "Failed to set src buftype %d, format %#x %dx%d\n", buf_type, pixfmt, width, height); return rv; } int mediabufs_ctl_query_ext_ctrls(struct mediabufs_ctl * mbc, struct v4l2_query_ext_ctrl * ctrls, unsigned int n) { int rv = 0; while (n--) { while (ioctl(mbc->vfd, VIDIOC_QUERY_EXT_CTRL, ctrls)) { const int err = errno; if (err != EINTR) { // Often used for probing - errors are to be expected request_debug(mbc->dc, "Failed to query ext id=%#x, err=%d\n", ctrls->id, err); ctrls->type = 0; // 0 is invalid rv = -err; break; } } ++ctrls; } return rv; } int mediabufs_src_resizable(const struct mediabufs_ctl *const mbc) { // Single planar OUTPUT can only take exact size buffers // Multiplanar will take larger than negotiated return V4L2_TYPE_IS_MULTIPLANAR(mbc->src_fmt.type); } static void mediabufs_ctl_delete(struct mediabufs_ctl *const mbc) { if (!mbc) return; // Break the weak link first ff_weak_link_break(&mbc->this_wlm); polltask_delete(&mbc->pt); mediabufs_stream_off(mbc); // Empty v4l2 buffer stash request_buffers(mbc->vfd, mbc->src_fmt.type, V4L2_MEMORY_MMAP, 0); request_buffers(mbc->vfd, mbc->dst_fmt.type, V4L2_MEMORY_MMAP, 0); bq_free_all_free_src(mbc->src); bq_free_all_inuse_src(mbc->src); bq_free_all_free_dst(mbc->dst); { struct qent_dst *dst_be; while ((dst_be = base_to_dst(bq_get_inuse(mbc->dst))) != NULL) { dst_be->base.timestamp = (struct timeval){0}; dst_be->base.status = QENT_ERROR; qe_dst_done(dst_be); } } queue_delete(mbc->dst); queue_delete(mbc->src); close(mbc->vfd); pthread_mutex_destroy(&mbc->lock); free(mbc); } struct mediabufs_ctl * mediabufs_ctl_ref(struct mediabufs_ctl *const mbc) { atomic_fetch_add(&mbc->ref_count, 1); return mbc; } void mediabufs_ctl_unref(struct mediabufs_ctl **const pmbc) { struct mediabufs_ctl *const mbc = *pmbc; int n; if (!mbc) return; *pmbc = NULL; n = atomic_fetch_sub(&mbc->ref_count, 1); if (n) return; mediabufs_ctl_delete(mbc); } unsigned int mediabufs_ctl_driver_version(struct mediabufs_ctl *const mbc) { return mbc->capability.version; } static int set_capabilities(struct mediabufs_ctl *const mbc) { uint32_t caps; if (ioctl(mbc->vfd, VIDIOC_QUERYCAP, &mbc->capability)) { int err = errno; request_err(mbc->dc, "Failed to get capabilities: %s\n", strerror(err)); return -err; } caps = (mbc->capability.capabilities & V4L2_CAP_DEVICE_CAPS) != 0 ? mbc->capability.device_caps : mbc->capability.capabilities; if ((caps & V4L2_CAP_VIDEO_M2M_MPLANE) != 0) { mbc->src_fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; mbc->dst_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; } else if ((caps & V4L2_CAP_VIDEO_M2M) != 0) { mbc->src_fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; mbc->dst_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; } else { request_err(mbc->dc, "No M2M capabilities (%#x)\n", caps); return -EINVAL; } return 0; } /* One of these per context */ struct mediabufs_ctl * mediabufs_ctl_new(void * const dc, const char * vpath, struct pollqueue *const pq) { struct mediabufs_ctl *const mbc = calloc(1, sizeof(*mbc)); if (!mbc) return NULL; mbc->dc = dc; // Default mono planar mbc->pq = pq; pthread_mutex_init(&mbc->lock, NULL); /* Pick a default - could we scan for this? */ if (vpath == NULL) vpath = "/dev/media0"; while ((mbc->vfd = open(vpath, O_RDWR)) == -1) { const int err = errno; if (err != EINTR) { request_err(dc, "Failed to open video dev '%s': %s\n", vpath, strerror(err)); goto fail0; } } if (set_capabilities(mbc)) { request_err(dc, "Bad capabilities for video dev '%s'\n", vpath); goto fail1; } mbc->src = queue_new(mbc->vfd); if (!mbc->src) goto fail1; mbc->dst = queue_new(mbc->vfd); if (!mbc->dst) goto fail2; mbc->pt = polltask_new(pq, mbc->vfd, POLLIN | POLLOUT, mediabufs_poll_cb, mbc); if (!mbc->pt) goto fail3; mbc->this_wlm = ff_weak_link_new(mbc); if (!mbc->this_wlm) goto fail4; /* Cannot add polltask now - polling with nothing pending * generates infinite error polls */ return mbc; fail4: polltask_delete(&mbc->pt); fail3: queue_delete(mbc->dst); fail2: queue_delete(mbc->src); fail1: close(mbc->vfd); fail0: free(mbc); request_info(dc, "%s: FAILED\n", __func__); return NULL; }