From accf519d251f366afbbbffb67ebac3553a3c7cbb Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 3 Dec 2019 21:01:18 +0100 Subject: [PATCH] Add V4L2 m2m deinterlace filter Signed-off-by: Alex Bee --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_deinterlace_v4l2m2m.c | 1011 ++++++++++++++++++++++++++ 3 files changed, 1013 insertions(+) create mode 100644 libavfilter/vf_deinterlace_v4l2m2m.c diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 91487afb21..84adb9ba46 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -272,6 +272,7 @@ OBJS-$(CONFIG_DEFLATE_FILTER) += vf_neighbor.o OBJS-$(CONFIG_DEFLICKER_FILTER) += vf_deflicker.o OBJS-$(CONFIG_DEINTERLACE_QSV_FILTER) += vf_vpp_qsv.o OBJS-$(CONFIG_DEINTERLACE_VAAPI_FILTER) += vf_deinterlace_vaapi.o vaapi_vpp.o +OBJS-$(CONFIG_DEINTERLACE_V4L2M2M_FILTER) += vf_deinterlace_v4l2m2m.o OBJS-$(CONFIG_DEJUDDER_FILTER) += vf_dejudder.o OBJS-$(CONFIG_DELOGO_FILTER) += vf_delogo.o OBJS-$(CONFIG_DENOISE_VAAPI_FILTER) += vf_misc_vaapi.o vaapi_vpp.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 9819f0f95b..c6d93340e6 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -248,6 +248,7 @@ extern const AVFilter ff_vf_dedot; extern const AVFilter ff_vf_deflate; extern const AVFilter ff_vf_deflicker; extern const AVFilter ff_vf_deinterlace_qsv; +extern const AVFilter ff_vf_deinterlace_v4l2m2m; extern const AVFilter ff_vf_deinterlace_vaapi; extern const AVFilter ff_vf_dejudder; extern const AVFilter ff_vf_delogo; diff --git a/libavfilter/vf_deinterlace_v4l2m2m.c b/libavfilter/vf_deinterlace_v4l2m2m.c new file mode 100644 index 0000000000..bf163279ce --- /dev/null +++ b/libavfilter/vf_deinterlace_v4l2m2m.c @@ -0,0 +1,1011 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * deinterlace video filter - V4L2 M2M + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/common.h" +#include "libavutil/hwcontext.h" +#include "libavutil/hwcontext_drm.h" +#include "libavutil/internal.h" +#include "libavutil/mathematics.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/time.h" + +#include "avfilter.h" +#include "formats.h" +#include "video.h" + +typedef struct V4L2Queue V4L2Queue; +typedef struct DeintV4L2M2MContextShared DeintV4L2M2MContextShared; + +typedef struct V4L2PlaneInfo { + int bytesperline; + size_t length; +} V4L2PlaneInfo; + +typedef struct V4L2Buffer { + int enqueued; + int reenqueue; + int fd; + struct v4l2_buffer buffer; + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + int num_planes; + V4L2PlaneInfo plane_info[VIDEO_MAX_PLANES]; + AVDRMFrameDescriptor drm_frame; + V4L2Queue *q; +} V4L2Buffer; + +typedef struct V4L2Queue { + struct v4l2_format format; + int num_buffers; + V4L2Buffer *buffers; + DeintV4L2M2MContextShared *ctx; +} V4L2Queue; + +typedef struct DeintV4L2M2MContextShared { + int fd; + int done; + int width; + int height; + int orig_width; + int orig_height; + uint64_t drm_in_format; + uint64_t drm_out_format; + + atomic_uint refcount; + + AVBufferRef *hw_frames_ctx; + + /* + * TODO: check if its really neccessary to hold this + * ref, it's only used for freeing av_frame on decoding + * end/abort + */ + AVFrame *cur_in_frame; + AVFrame *prev_in_frame; + unsigned int field_order; + + V4L2Queue output; + V4L2Queue capture; +} DeintV4L2M2MContextShared; + +typedef struct DeintV4L2M2MContext { + const AVClass *class; + + DeintV4L2M2MContextShared *shared; +} DeintV4L2M2MContext; + +static inline uint32_t v4l2_pix_fmt_from_drm_format(uint64_t drm_format) +{ + switch(drm_format) { +#if defined(V4L2_PIX_FMT_SUNXI_TILED_NV12) && defined(DRM_FORMAT_MOD_ALLWINNER_TILED) + case DRM_FORMAT_MOD_ALLWINNER_TILED: + return V4L2_PIX_FMT_SUNXI_TILED_NV12; +#endif + case DRM_FORMAT_NV12: + return V4L2_PIX_FMT_NV12; +#if defined(V4L2_PIX_FMT_NV15) && defined(DRM_FORMAT_NV15) + case DRM_FORMAT_NV15: + return V4L2_PIX_FMT_NV15; +#endif + case DRM_FORMAT_NV16: + return V4L2_PIX_FMT_NV16; +#if defined(V4L2_PIX_FMT_NV20) && defined(DRM_FORMAT_NV20) + case DRM_FORMAT_NV20: + return V4L2_PIX_FMT_NV20; +#endif + case DRM_FORMAT_NV21: + return V4L2_PIX_FMT_NV21; + case DRM_FORMAT_NV61: + return V4L2_PIX_FMT_NV61; + default: + av_log(NULL, AV_LOG_WARNING, "%s unknown drm format %s using default v4l2_pix_fmt %s\n", + __func__ , av_fourcc2str(drm_format), av_fourcc2str(V4L2_PIX_FMT_NV12)); + return V4L2_PIX_FMT_NV12; + } +} + +static inline uint64_t drm_format_from_v4l2_pix_fmt(uint32_t v4l2_pix_fmt) +{ + switch(v4l2_pix_fmt) { + case V4L2_PIX_FMT_NV12: + return DRM_FORMAT_NV12; +#if defined(V4L2_PIX_FMT_NV15) && defined(DRM_FORMAT_NV15) + case V4L2_PIX_FMT_NV15: + return DRM_FORMAT_NV15; +#endif + case V4L2_PIX_FMT_NV16: + return DRM_FORMAT_NV16; +#if defined(V4L2_PIX_FMT_NV20) && defined(DRM_FORMAT_NV20) + case V4L2_PIX_FMT_NV20: + return DRM_FORMAT_NV20; +#endif + case V4L2_PIX_FMT_NV21: + return DRM_FORMAT_NV21; + case V4L2_PIX_FMT_NV61: + return DRM_FORMAT_NV61; +#if defined(V4L2_PIX_FMT_SUNXI_TILED_NV12) && defined(DRM_FORMAT_MOD_ALLWINNER_TILED) + case V4L2_PIX_FMT_SUNXI_TILED_NV12: + return DRM_FORMAT_MOD_ALLWINNER_TILED; +#endif + default: + av_log(NULL, AV_LOG_WARNING, "%s unknown v4l2_pix_fmt format 0x%x using default drm_format 0x%x\n", + __func__ , v4l2_pix_fmt, DRM_FORMAT_NV12); + return DRM_FORMAT_NV12; + } +} + +static inline uint64_t drm_format_modifier(uint64_t drm_format) +{ +#ifdef DRM_FORMAT_MOD_ALLWINNER_TILED + if (drm_format == DRM_FORMAT_MOD_ALLWINNER_TILED) + return DRM_FORMAT_MOD_ALLWINNER_TILED; +#endif + return DRM_FORMAT_MOD_LINEAR; + +} + +static int deint_v4l2m2m_prepare_context(DeintV4L2M2MContextShared *ctx) +{ + struct v4l2_capability cap; + int ret; + + memset(&cap, 0, sizeof(cap)); + ret = ioctl(ctx->fd, VIDIOC_QUERYCAP, &cap); + if (ret < 0) + return ret; + + if (!(cap.capabilities & V4L2_CAP_STREAMING)) + return AVERROR(EINVAL); + + if (cap.capabilities & V4L2_CAP_VIDEO_M2M) { + ctx->capture.format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ctx->output.format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + + return 0; + } + + if (cap.capabilities & V4L2_CAP_VIDEO_M2M_MPLANE) { + ctx->capture.format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + ctx->output.format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + + return 0; + } + + return AVERROR(EINVAL); +} + +static int deint_v4l2m2m_try_format(V4L2Queue *queue, uint64_t drm_format) +{ + struct v4l2_format *fmt = &queue->format; + DeintV4L2M2MContextShared *ctx = queue->ctx; + int ret, field; + uint32_t v4l2_pix_fmt = v4l2_pix_fmt_from_drm_format(drm_format); + + ret = ioctl(ctx->fd, VIDIOC_G_FMT, fmt); + if (ret) + av_log(NULL, AV_LOG_ERROR, "VIDIOC_G_FMT failed: %d\n", ret); + + if (V4L2_TYPE_IS_OUTPUT(fmt->type)) + field = V4L2_FIELD_INTERLACED_TB; + else + field = V4L2_FIELD_NONE; + + if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { + fmt->fmt.pix_mp.pixelformat = v4l2_pix_fmt; + fmt->fmt.pix_mp.field = field; + fmt->fmt.pix_mp.width = ctx->width; + fmt->fmt.pix_mp.height = ctx->height; + } else { + fmt->fmt.pix.pixelformat = v4l2_pix_fmt; + fmt->fmt.pix.field = field; + fmt->fmt.pix.width = ctx->width; + fmt->fmt.pix.height = ctx->height; + } + + ret = ioctl(ctx->fd, VIDIOC_TRY_FMT, fmt); + if (ret) + return AVERROR(EINVAL); + + if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { + if (fmt->fmt.pix_mp.pixelformat != v4l2_pix_fmt || + fmt->fmt.pix_mp.field != field) { + av_log(NULL, AV_LOG_DEBUG, "format not supported for type %d\n", fmt->type); + + return AVERROR(EINVAL); + } + } else { + if (fmt->fmt.pix.pixelformat != v4l2_pix_fmt || + fmt->fmt.pix.field != field) { + av_log(NULL, AV_LOG_DEBUG, "format not supported for type %d\n", fmt->type); + + return AVERROR(EINVAL); + } + } + + return 0; +} +static int deint_v4l2m2m_set_format(V4L2Queue *queue, uint32_t field, int width, int height, uint64_t drm_format) +{ + struct v4l2_format *fmt = &queue->format; + DeintV4L2M2MContextShared *ctx = queue->ctx; + int ret; + uint32_t v4l2_pix_fmt = v4l2_pix_fmt_from_drm_format(drm_format); + + if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { + fmt->fmt.pix_mp.pixelformat = v4l2_pix_fmt; + fmt->fmt.pix_mp.field = field; + fmt->fmt.pix_mp.width = width; + fmt->fmt.pix_mp.height = height; + /* TODO: bytesperline and imagesize */ + } else { + fmt->fmt.pix.pixelformat = v4l2_pix_fmt; + fmt->fmt.pix.field = field; + fmt->fmt.pix.width = width; + fmt->fmt.pix.height = height; + fmt->fmt.pix.sizeimage = 0; + fmt->fmt.pix.bytesperline = 0; + } + + ret = ioctl(ctx->fd, VIDIOC_S_FMT, fmt); + if (ret) + av_log(NULL, AV_LOG_ERROR, "VIDIOC_S_FMT failed: %d\n", ret); + + else if (!V4L2_TYPE_IS_OUTPUT(queue->format.type)) { + if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type) && fmt->fmt.pix_mp.pixelformat != v4l2_pix_fmt) { + ctx->drm_out_format = drm_format_from_v4l2_pix_fmt(fmt->fmt.pix_mp.pixelformat); + av_log(NULL, AV_LOG_DEBUG, "%s driver updated v4l2_pixfmt from: %s to %s, so now using %s as drm output format\n", + __func__, av_fourcc2str(v4l2_pix_fmt), av_fourcc2str(fmt->fmt.pix_mp.pixelformat), av_fourcc2str(ctx->drm_out_format)); + } else if (fmt->fmt.pix.pixelformat != v4l2_pix_fmt) { + ctx->drm_out_format = drm_format_from_v4l2_pix_fmt(fmt->fmt.pix.pixelformat); + av_log(NULL, AV_LOG_DEBUG, "%s driver updated v4l2_pixfmt from: %s to %s, so now using %s as drm output format\n", + __func__, av_fourcc2str(v4l2_pix_fmt), av_fourcc2str(fmt->fmt.pix.pixelformat), av_fourcc2str(ctx->drm_out_format)); + } + } + + return ret; +} + +static int deint_v4l2m2m_probe_device(DeintV4L2M2MContextShared *ctx, char *node) +{ + int ret; + + ctx->fd = open(node, O_RDWR | O_NONBLOCK, 0); + if (ctx->fd < 0) + return AVERROR(errno); + + ret = deint_v4l2m2m_prepare_context(ctx); + if (ret) + goto fail; + + ret = deint_v4l2m2m_try_format(&ctx->capture, ctx->drm_out_format); + if (ret) + goto fail; + + ret = deint_v4l2m2m_try_format(&ctx->output, ctx->drm_in_format); + if (ret) + goto fail; + + return 0; + +fail: + close(ctx->fd); + ctx->fd = -1; + + return ret; +} + +static int deint_v4l2m2m_find_device(DeintV4L2M2MContextShared *ctx) +{ + int ret = AVERROR(EINVAL); + struct dirent *entry; + char node[PATH_MAX]; + DIR *dirp; + + dirp = opendir("/dev"); + if (!dirp) + return AVERROR(errno); + + for (entry = readdir(dirp); entry; entry = readdir(dirp)) { + + if (strncmp(entry->d_name, "video", 5)) + continue; + + snprintf(node, sizeof(node), "/dev/%s", entry->d_name); + av_log(NULL, AV_LOG_DEBUG, "probing device %s\n", node); + ret = deint_v4l2m2m_probe_device(ctx, node); + if (!ret) + break; + } + + closedir(dirp); + + if (ret) { + av_log(NULL, AV_LOG_ERROR, "Could not find a valid device\n"); + ctx->fd = -1; + + return ret; + } + + av_log(NULL, AV_LOG_INFO, "Using device %s\n", node); + + return 0; +} + +static int deint_v4l2m2m_enqueue_buffer(V4L2Buffer *buf) +{ + int ret; + + ret = ioctl(buf->q->ctx->fd, VIDIOC_QBUF, &buf->buffer); + if (ret < 0) + return AVERROR(errno); + + buf->enqueued = 1; + + return 0; +} + +static int v4l2_buffer_export_drm(V4L2Buffer* avbuf, uint64_t drm_format) +{ + struct v4l2_exportbuffer expbuf; + int i, ret; + + for (i = 0; i < avbuf->num_planes; i++) { + memset(&expbuf, 0, sizeof(expbuf)); + + expbuf.index = avbuf->buffer.index; + expbuf.type = avbuf->buffer.type; + expbuf.plane = i; + + ret = ioctl(avbuf->q->ctx->fd, VIDIOC_EXPBUF, &expbuf); + if (ret < 0) + return AVERROR(errno); + + avbuf->fd = expbuf.fd; + + if (V4L2_TYPE_IS_MULTIPLANAR(avbuf->buffer.type)) { + /* drm frame */ + avbuf->drm_frame.objects[i].size = avbuf->buffer.m.planes[i].length; + avbuf->drm_frame.objects[i].fd = expbuf.fd; + avbuf->drm_frame.objects[i].format_modifier = drm_format_modifier(drm_format); + } else { + /* drm frame */ + avbuf->drm_frame.objects[0].size = avbuf->buffer.length; + avbuf->drm_frame.objects[0].fd = expbuf.fd; + avbuf->drm_frame.objects[0].format_modifier = drm_format_modifier(drm_format); + } + } + + return 0; +} + +static int deint_v4l2m2m_allocate_buffers(V4L2Queue *queue) +{ + struct v4l2_format *fmt = &queue->format; + DeintV4L2M2MContextShared *ctx = queue->ctx; + struct v4l2_requestbuffers req; + int ret, i, j, multiplanar; + uint32_t memory; + + memory = V4L2_TYPE_IS_OUTPUT(fmt->type) ? + V4L2_MEMORY_DMABUF : V4L2_MEMORY_MMAP; + + multiplanar = V4L2_TYPE_IS_MULTIPLANAR(fmt->type); + + memset(&req, 0, sizeof(req)); + req.count = queue->num_buffers; + req.memory = memory; + req.type = fmt->type; + + ret = ioctl(ctx->fd, VIDIOC_REQBUFS, &req); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "VIDIOC_REQBUFS failed: %s\n", strerror(errno)); + + return AVERROR(errno); + } + + queue->num_buffers = req.count; + queue->buffers = av_mallocz(queue->num_buffers * sizeof(V4L2Buffer)); + if (!queue->buffers) { + av_log(NULL, AV_LOG_ERROR, "malloc enomem\n"); + + return AVERROR(ENOMEM); + } + + for (i = 0; i < queue->num_buffers; i++) { + V4L2Buffer *buf = &queue->buffers[i]; + + buf->enqueued = 0; + buf->fd = -1; + buf->q = queue; + + buf->buffer.type = fmt->type; + buf->buffer.memory = memory; + buf->buffer.index = i; + + if (multiplanar) { + buf->buffer.length = VIDEO_MAX_PLANES; + buf->buffer.m.planes = buf->planes; + } + + ret = ioctl(ctx->fd, VIDIOC_QUERYBUF, &buf->buffer); + if (ret < 0) { + ret = AVERROR(errno); + + goto fail; + } + + if (multiplanar) + buf->num_planes = buf->buffer.length; + else + buf->num_planes = 1; + + for (j = 0; j < buf->num_planes; j++) { + V4L2PlaneInfo *info = &buf->plane_info[j]; + + if (multiplanar) { + info->bytesperline = fmt->fmt.pix_mp.plane_fmt[j].bytesperline; + info->length = buf->buffer.m.planes[j].length; + } else { + info->bytesperline = fmt->fmt.pix.bytesperline; + info->length = buf->buffer.length; + } + } + + if (!V4L2_TYPE_IS_OUTPUT(fmt->type)) { + ret = deint_v4l2m2m_enqueue_buffer(buf); + if (ret) + goto fail; + + ret = v4l2_buffer_export_drm(buf, ctx->drm_out_format); + if (ret) + goto fail; + } + } + + return 0; + +fail: + for (i = 0; i < queue->num_buffers; i++) + if (queue->buffers[i].fd >= 0) + close(queue->buffers[i].fd); + av_free(queue->buffers); + queue->buffers = NULL; + + return ret; +} + +static int deint_v4l2m2m_streamon(V4L2Queue *queue) +{ + int type = queue->format.type; + int ret; + + ret = ioctl(queue->ctx->fd, VIDIOC_STREAMON, &type); + if (ret < 0) + return AVERROR(errno); + + return 0; +} + +static int deint_v4l2m2m_streamoff(V4L2Queue *queue) +{ + int type = queue->format.type; + int ret; + + ret = ioctl(queue->ctx->fd, VIDIOC_STREAMOFF, &type); + if (ret < 0) + return AVERROR(errno); + + return 0; +} + +static V4L2Buffer* deint_v4l2m2m_dequeue_buffer(V4L2Queue *queue, int timeout) +{ + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + DeintV4L2M2MContextShared *ctx = queue->ctx; + struct v4l2_buffer buf = { 0 }; + V4L2Buffer* avbuf = NULL; + struct pollfd pfd; + short events; + int ret; + + if (V4L2_TYPE_IS_OUTPUT(queue->format.type)) + events = POLLOUT | POLLWRNORM; + else + events = POLLIN | POLLRDNORM; + + pfd.events = events; + pfd.fd = ctx->fd; + + for (;;) { + ret = poll(&pfd, 1, timeout); + if (ret > 0) + break; + if (errno == EINTR) + continue; + return NULL; + } + + if (pfd.revents & POLLERR) + return NULL; + + if (pfd.revents & events) { + memset(&buf, 0, sizeof(buf)); + buf.memory = V4L2_MEMORY_MMAP; + buf.type = queue->format.type; + if (V4L2_TYPE_IS_MULTIPLANAR(queue->format.type)) { + memset(planes, 0, sizeof(planes)); + buf.length = VIDEO_MAX_PLANES; + buf.m.planes = planes; + } + + ret = ioctl(ctx->fd, VIDIOC_DQBUF, &buf); + if (ret) { + if (errno != EAGAIN) + av_log(NULL, AV_LOG_DEBUG, "VIDIOC_DQBUF, errno (%s)\n", + av_err2str(AVERROR(errno))); + return NULL; + } + + avbuf = &queue->buffers[buf.index]; + avbuf->enqueued = 0; + avbuf->buffer = buf; + if (V4L2_TYPE_IS_MULTIPLANAR(queue->format.type)) { + memcpy(avbuf->planes, planes, sizeof(planes)); + avbuf->buffer.m.planes = avbuf->planes; + } + + return avbuf; + } + + return NULL; +} + +static V4L2Buffer *deint_v4l2m2m_find_free_buf(V4L2Queue *queue) +{ + int i; + + for (i = 0; i < queue->num_buffers; i++) + if (!queue->buffers[i].enqueued) + return &queue->buffers[i]; + + return NULL; +} + +static int deint_v4l2m2m_enqueue(V4L2Queue *queue, const AVFrame* frame) +{ + AVDRMFrameDescriptor *drm_desc = (AVDRMFrameDescriptor *)frame->data[0]; + V4L2Buffer *buf; + int i; + + if (V4L2_TYPE_IS_OUTPUT(queue->format.type)) + while (deint_v4l2m2m_dequeue_buffer(queue, 0)); + + buf = deint_v4l2m2m_find_free_buf(queue); + if (!buf) + return AVERROR(ENOMEM); + + if (V4L2_TYPE_IS_MULTIPLANAR(buf->buffer.type)) + for (i = 0; i < drm_desc->nb_objects; i++) + buf->buffer.m.planes[i].m.fd = drm_desc->objects[i].fd; + else + buf->buffer.m.fd = drm_desc->objects[0].fd; + + return deint_v4l2m2m_enqueue_buffer(buf); +} + +static void deint_v4l2m2m_destroy_context(DeintV4L2M2MContextShared *ctx) +{ + if (atomic_fetch_sub(&ctx->refcount, 1) == 1) { + V4L2Queue *capture = &ctx->capture; + V4L2Queue *output = &ctx->output; + int i; + + av_log(NULL, AV_LOG_DEBUG, "%s - destroying context\n", __func__); + + if (ctx->fd >= 0) { + deint_v4l2m2m_streamoff(capture); + deint_v4l2m2m_streamoff(output); + } + + if (capture->buffers) + for (i = 0; i < capture->num_buffers; i++) { + capture->buffers[i].q = NULL; + if (capture->buffers[i].fd >= 0) + close(capture->buffers[i].fd); + } + + if (ctx->cur_in_frame) + av_frame_free(&ctx->cur_in_frame); + + if (ctx->prev_in_frame) + av_frame_free(&ctx->prev_in_frame); + + av_buffer_unref(&ctx->hw_frames_ctx); + + if (capture->buffers) + av_free(capture->buffers); + + if (output->buffers) + av_free(output->buffers); + + if (ctx->fd >= 0) { + close(ctx->fd); + ctx->fd = -1; + } + + av_free(ctx); + } +} + +static void v4l2_free_buffer(void *opaque, uint8_t *unused) +{ + V4L2Buffer *buf = opaque; + DeintV4L2M2MContextShared *ctx = buf->q->ctx; + + if (!ctx->done) + deint_v4l2m2m_enqueue_buffer(buf); + + deint_v4l2m2m_destroy_context(ctx); +} + +static uint8_t *v4l2_get_drm_frame(V4L2Buffer *avbuf, int height, uint64_t drm_format) +{ + AVDRMFrameDescriptor *drm_desc = &avbuf->drm_frame; + AVDRMLayerDescriptor *layer; + + /* fill the DRM frame descriptor */ + drm_desc->nb_objects = avbuf->num_planes; + drm_desc->nb_layers = 1; + + layer = &drm_desc->layers[0]; + layer->nb_planes = avbuf->num_planes; + + for (int i = 0; i < avbuf->num_planes; i++) { + layer->planes[i].object_index = i; + layer->planes[i].offset = 0; + layer->planes[i].pitch = avbuf->plane_info[i].bytesperline; + } + + layer->format = drm_format; + + if (avbuf->num_planes == 1) { + layer->nb_planes = 2; + + layer->planes[1].object_index = 0; + layer->planes[1].offset = avbuf->plane_info[0].bytesperline * height; + layer->planes[1].pitch = avbuf->plane_info[0].bytesperline; + } + + return (uint8_t *)drm_desc; +} + +static int deint_v4l2m2m_dequeue_frame(V4L2Queue *queue, AVFrame* frame, int timeout) +{ + DeintV4L2M2MContextShared *ctx = queue->ctx; + V4L2Buffer* avbuf; + + avbuf = deint_v4l2m2m_dequeue_buffer(queue, timeout); + if (!avbuf) { + av_log(NULL, AV_LOG_ERROR, "dequeueing failed\n"); + return AVERROR(EINVAL); + } + + frame->buf[0] = av_buffer_create((uint8_t *) &avbuf->drm_frame, + sizeof(avbuf->drm_frame), v4l2_free_buffer, + avbuf, AV_BUFFER_FLAG_READONLY); + if (!frame->buf[0]) + return AVERROR(ENOMEM); + + atomic_fetch_add(&ctx->refcount, 1); + + frame->data[0] = (uint8_t *)v4l2_get_drm_frame(avbuf, ctx->orig_height, ctx->drm_out_format); + frame->format = AV_PIX_FMT_DRM_PRIME; + frame->hw_frames_ctx = av_buffer_ref(ctx->hw_frames_ctx); + frame->height = ctx->height; + frame->width = ctx->width; + + if (avbuf->buffer.flags & V4L2_BUF_FLAG_ERROR) { + av_log(NULL, AV_LOG_ERROR, "driver decode error\n"); + frame->decode_error_flags |= FF_DECODE_ERROR_INVALID_BITSTREAM; + } + + return 0; +} + +static int deint_v4l2m2m_dequeue(AVFilterContext *avctx, AVFrame *input_frame) +{ + DeintV4L2M2MContext *priv = avctx->priv; + DeintV4L2M2MContextShared *ctx = priv->shared; + AVFilterLink *outlink = avctx->outputs[0]; + AVFrame *output_frame_1, *output_frame_2; + int64_t first_pts = AV_NOPTS_VALUE; + int err; + + av_log(priv, AV_LOG_DEBUG, "input pts: %"PRId64" (field %d)\n", + input_frame->pts, ctx->field_order); + + output_frame_1 = av_frame_alloc(); + if (!output_frame_1) + return AVERROR(ENOMEM); + + err = deint_v4l2m2m_dequeue_frame(&ctx->capture, output_frame_1, 500); + if (err < 0) { + av_log(priv, AV_LOG_ERROR, "no 1st frame (field %d)\n", ctx->field_order); + goto fail_out1; + } + + err = av_frame_copy_props(output_frame_1, input_frame); + if (err < 0) + goto fail_out1; + + output_frame_1->flags &= ~AV_FRAME_FLAG_INTERLACED; + + output_frame_2 = av_frame_alloc(); + if (!output_frame_2) { + err = AVERROR(ENOMEM); + goto fail_out1; + } + + err = deint_v4l2m2m_dequeue_frame(&ctx->capture, output_frame_2, 500); + if (err < 0) { + av_log(priv, AV_LOG_ERROR, "no 2nd frame (field %d)\n", ctx->field_order); + goto fail_out2; + } + + err = av_frame_copy_props(output_frame_2, input_frame); + if (err < 0) + goto fail_out2; + + output_frame_2->flags &= ~AV_FRAME_FLAG_INTERLACED; + + if (ctx->prev_in_frame && ctx->prev_in_frame->pts != AV_NOPTS_VALUE + && input_frame->pts != AV_NOPTS_VALUE) { + first_pts = (ctx->prev_in_frame->pts + input_frame->pts) / 2; + av_log(priv, AV_LOG_DEBUG, "calculated first pts %"PRId64"\n", first_pts); + } + + output_frame_1->pts = first_pts; + + err = ff_filter_frame(outlink, output_frame_1); + if (err < 0) { + av_frame_free(&output_frame_2); + return err; + } + err = ff_filter_frame(outlink, output_frame_2); + + if (err < 0) + return err; + + av_log(priv, AV_LOG_DEBUG, "1st frame pts: %"PRId64" 2nd frame pts: %"PRId64" first pts: %"PRId64" (field %d)\n", + output_frame_1->pts, output_frame_2->pts, first_pts, ctx->field_order); + + return 0; + +fail_out2: + av_frame_free(&output_frame_2); +fail_out1: + av_frame_free(&output_frame_1); + return err; +} + +static int deint_v4l2m2m_config_props(AVFilterLink *outlink) +{ + AVFilterLink *inlink = outlink->src->inputs[0]; + AVFilterContext *avctx = outlink->src; + DeintV4L2M2MContext *priv = avctx->priv; + DeintV4L2M2MContextShared *ctx = priv->shared; + FilterLink *fl_inlink = ff_filter_link(inlink); + FilterLink *fl_outlink = ff_filter_link(outlink); + int ret; + + ctx->height = avctx->inputs[0]->h; + ctx->width = avctx->inputs[0]->w; + + fl_outlink->frame_rate = av_mul_q(fl_inlink->frame_rate, + (AVRational){ 2, 1 }); + outlink->time_base = av_mul_q(inlink->time_base, + (AVRational){ 1, 2 }); + + ret = deint_v4l2m2m_find_device(ctx); + if (ret) + return ret; + + if (!fl_inlink->hw_frames_ctx) { + av_log(priv, AV_LOG_ERROR, "No hw context provided on input\n"); + return AVERROR(EINVAL); + } + + ctx->hw_frames_ctx = av_buffer_ref(fl_inlink->hw_frames_ctx); + if (!ctx->hw_frames_ctx) + return AVERROR(ENOMEM); + + return 0; +} + +static int deint_v4l2m2m_query_formats(AVFilterContext *avctx) +{ + static const enum AVPixelFormat pixel_formats[] = { + AV_PIX_FMT_DRM_PRIME, + AV_PIX_FMT_NONE, + }; + + return ff_set_common_formats(avctx, ff_make_format_list(pixel_formats)); +} + +static int deint_v4l2m2m_filter_frame(AVFilterLink *link, AVFrame *in) +{ + AVFilterContext *avctx = link->dst; + DeintV4L2M2MContext *priv = avctx->priv; + DeintV4L2M2MContextShared *ctx = priv->shared; + V4L2Queue *capture = &ctx->capture; + V4L2Queue *output = &ctx->output; + int ret; + + av_log(priv, AV_LOG_DEBUG, "input pts: %"PRId64" field :%d interlaced: %d\n", + in->pts, !!(in->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST), !!(in->flags & AV_FRAME_FLAG_INTERLACED)); + + ctx->cur_in_frame = in; + + if (ctx->field_order == V4L2_FIELD_ANY) { + AVDRMFrameDescriptor *drm_desc = (AVDRMFrameDescriptor *)in->data[0]; + ctx->orig_width = drm_desc->layers[0].planes[0].pitch; + ctx->orig_height = drm_desc->layers[0].planes[1].offset / ctx->orig_width; + ctx->drm_in_format = drm_desc->layers->format; + ctx->drm_out_format = drm_desc->layers->format; + + if (!!(in->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST)) + ctx->field_order = V4L2_FIELD_INTERLACED_TB; + else + ctx->field_order = V4L2_FIELD_INTERLACED_BT; + + ret = deint_v4l2m2m_set_format(output, ctx->field_order, + ctx->orig_width, ctx->orig_height, + ctx->drm_in_format); + if (ret) + return ret; + + ret = deint_v4l2m2m_set_format(capture, V4L2_FIELD_NONE, + ctx->orig_width, ctx->orig_height, + ctx->drm_out_format); + if (ret) + return ret; + + ret = deint_v4l2m2m_allocate_buffers(capture); + if (ret) + return ret; + + ret = deint_v4l2m2m_streamon(capture); + if (ret) + return ret; + + ret = deint_v4l2m2m_allocate_buffers(output); + if (ret) + return ret; + + ret = deint_v4l2m2m_streamon(output); + if (ret) + return ret; + } + + ret = deint_v4l2m2m_enqueue(output, in); + if (ret) + return ret; + + ret = deint_v4l2m2m_dequeue(avctx, in); + if (ret) + return ret; + + if (ctx->prev_in_frame) + av_frame_free(&ctx->prev_in_frame); + + ctx->prev_in_frame = in; + ctx->cur_in_frame = NULL; + + return 0; +} + +static av_cold int deint_v4l2m2m_init(AVFilterContext *avctx) +{ + DeintV4L2M2MContext *priv = avctx->priv; + DeintV4L2M2MContextShared *ctx; + + ctx = av_mallocz(sizeof(DeintV4L2M2MContextShared)); + if (!ctx) + return AVERROR(ENOMEM); + + priv->shared = ctx; + ctx->fd = -1; + ctx->output.ctx = ctx; + ctx->output.num_buffers = 6; + ctx->capture.ctx = ctx; + ctx->capture.num_buffers = 6; + ctx->done = 0; + ctx->field_order = V4L2_FIELD_ANY; + ctx->cur_in_frame = NULL; + ctx->prev_in_frame = NULL; + ctx->drm_in_format = DRM_FORMAT_NV12; + ctx->drm_out_format = DRM_FORMAT_NV12; + atomic_init(&ctx->refcount, 1); + + return 0; +} + +static void deint_v4l2m2m_uninit(AVFilterContext *avctx) +{ + DeintV4L2M2MContext *priv = avctx->priv; + DeintV4L2M2MContextShared *ctx = priv->shared; + + ctx->done = 1; + deint_v4l2m2m_destroy_context(ctx); +} + +static const AVOption deinterlace_v4l2m2m_options[] = { + { NULL }, +}; + +AVFILTER_DEFINE_CLASS(deinterlace_v4l2m2m); + +static const AVFilterPad deint_v4l2m2m_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = deint_v4l2m2m_filter_frame, + }, +}; + +static const AVFilterPad deint_v4l2m2m_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = deint_v4l2m2m_config_props, + }, +}; + +AVFilter ff_vf_deinterlace_v4l2m2m = { + .name = "deinterlace_v4l2m2m", + .description = NULL_IF_CONFIG_SMALL("V4L2 M2M deinterlacer"), + .priv_size = sizeof(DeintV4L2M2MContext), + .init = &deint_v4l2m2m_init, + .uninit = &deint_v4l2m2m_uninit, + FILTER_QUERY_FUNC(&deint_v4l2m2m_query_formats), + FILTER_INPUTS(deint_v4l2m2m_inputs), + FILTER_OUTPUTS(deint_v4l2m2m_outputs), + .priv_class = &deinterlace_v4l2m2m_class, +};