1835 lines
49 KiB
C
1835 lines
49 KiB
C
/*
|
|
* Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
|
|
*
|
|
* 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 <errno.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <pthread.h>
|
|
#include <semaphore.h>
|
|
#include <stdatomic.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <linux/media.h>
|
|
#include <linux/mman.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/select.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
|
|
#include <linux/videodev2.h>
|
|
|
|
#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;
|
|
}
|
|
|
|
|
|
|