407 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			407 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * V4L mem2mem
 | |
|  *
 | |
|  * Copyright (C) 2017 Alexis Ballier <aballier@gentoo.org>
 | |
|  * Copyright (C) 2017 Jorge Ramirez <jorge.ramirez-ortiz@linaro.org>
 | |
|  *
 | |
|  * 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
 | |
|  */
 | |
| 
 | |
| #include <linux/videodev2.h>
 | |
| #include <sys/ioctl.h>
 | |
| #include <sys/mman.h>
 | |
| #include <unistd.h>
 | |
| #include <dirent.h>
 | |
| #include <fcntl.h>
 | |
| #include "libavcodec/avcodec.h"
 | |
| #include "libavcodec/internal.h"
 | |
| #include "libavutil/pixdesc.h"
 | |
| #include "libavutil/imgutils.h"
 | |
| #include "libavutil/pixfmt.h"
 | |
| #include "v4l2_context.h"
 | |
| #include "v4l2_fmt.h"
 | |
| #include "v4l2_m2m.h"
 | |
| 
 | |
| static inline int v4l2_splane_video(struct v4l2_capability *cap)
 | |
| {
 | |
|     if (cap->capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT) &&
 | |
|         cap->capabilities & V4L2_CAP_STREAMING)
 | |
|         return 1;
 | |
| 
 | |
|     if (cap->capabilities & V4L2_CAP_VIDEO_M2M)
 | |
|         return 1;
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static inline int v4l2_mplane_video(struct v4l2_capability *cap)
 | |
| {
 | |
|     if (cap->capabilities & (V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE) &&
 | |
|         cap->capabilities & V4L2_CAP_STREAMING)
 | |
|         return 1;
 | |
| 
 | |
|     if (cap->capabilities & V4L2_CAP_VIDEO_M2M_MPLANE)
 | |
|         return 1;
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static int v4l2_prepare_contexts(V4L2m2mContext* s)
 | |
| {
 | |
|     struct v4l2_capability cap;
 | |
|     int ret;
 | |
| 
 | |
|     s->capture.done = s->output.done = 0;
 | |
|     s->capture.name = "capture";
 | |
|     s->output.name = "output ";
 | |
|     atomic_init(&s->refcount, 0);
 | |
|     sem_init(&s->refsync, 0, 0);
 | |
| 
 | |
|     memset(&cap, 0, sizeof(cap));
 | |
|     ret = ioctl(s->fd, VIDIOC_QUERYCAP, &cap);
 | |
|     if (ret < 0)
 | |
|         return ret;
 | |
| 
 | |
|     av_log(s->avctx, AV_LOG_INFO, "driver '%s' on card '%s'\n", cap.driver, cap.card);
 | |
| 
 | |
|     if (v4l2_mplane_video(&cap)) {
 | |
|         s->capture.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
 | |
|         s->output.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     if (v4l2_splane_video(&cap)) {
 | |
|         s->capture.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 | |
|         s->output.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     return AVERROR(EINVAL);
 | |
| }
 | |
| 
 | |
| static int v4l2_probe_driver(V4L2m2mContext* s)
 | |
| {
 | |
|     int ret;
 | |
| 
 | |
|     s->fd = open(s->devname, O_RDWR | O_NONBLOCK, 0);
 | |
|     if (s->fd < 0)
 | |
|         return AVERROR(errno);
 | |
| 
 | |
|     ret = v4l2_prepare_contexts(s);
 | |
|     if (ret < 0)
 | |
|         goto done;
 | |
| 
 | |
|     ret = ff_v4l2_context_get_format(&s->output);
 | |
|     if (ret) {
 | |
|         av_log(s->avctx, AV_LOG_DEBUG, "v4l2 output format not supported\n");
 | |
|         goto done;
 | |
|     }
 | |
| 
 | |
|     ret = ff_v4l2_context_get_format(&s->capture);
 | |
|     if (ret) {
 | |
|         av_log(s->avctx, AV_LOG_DEBUG, "v4l2 capture format not supported\n");
 | |
|         goto done;
 | |
|     }
 | |
| 
 | |
| done:
 | |
|     if (close(s->fd) < 0) {
 | |
|         ret = AVERROR(errno);
 | |
|         av_log(s->avctx, AV_LOG_ERROR, "failure closing %s (%s)\n", s->devname, av_err2str(AVERROR(errno)));
 | |
|     }
 | |
| 
 | |
|     s->fd = -1;
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static int v4l2_configure_contexts(V4L2m2mContext* s)
 | |
| {
 | |
|     void *log_ctx = s->avctx;
 | |
|     int ret;
 | |
| 
 | |
|     s->fd = open(s->devname, O_RDWR | O_NONBLOCK, 0);
 | |
|     if (s->fd < 0)
 | |
|         return AVERROR(errno);
 | |
| 
 | |
|     ret = v4l2_prepare_contexts(s);
 | |
|     if (ret < 0)
 | |
|         goto error;
 | |
| 
 | |
|     ret = ff_v4l2_context_set_format(&s->output);
 | |
|     if (ret) {
 | |
|         av_log(log_ctx, AV_LOG_ERROR, "can't set v4l2 output format\n");
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     ret = ff_v4l2_context_set_format(&s->capture);
 | |
|     if (ret) {
 | |
|         av_log(log_ctx, AV_LOG_ERROR, "can't to set v4l2 capture format\n");
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     ret = ff_v4l2_context_init(&s->output);
 | |
|     if (ret) {
 | |
|         av_log(log_ctx, AV_LOG_ERROR, "no v4l2 output context's buffers\n");
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     /* decoder's buffers need to be updated at a later stage */
 | |
|     if (!av_codec_is_decoder(s->avctx->codec)) {
 | |
|         ret = ff_v4l2_context_init(&s->capture);
 | |
|         if (ret) {
 | |
|             av_log(log_ctx, AV_LOG_ERROR, "no v4l2 capture context's buffers\n");
 | |
|             goto error;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| 
 | |
| error:
 | |
|     if (close(s->fd) < 0) {
 | |
|         ret = AVERROR(errno);
 | |
|         av_log(log_ctx, AV_LOG_ERROR, "error closing %s (%s)\n",
 | |
|             s->devname, av_err2str(AVERROR(errno)));
 | |
|     }
 | |
|     s->fd = -1;
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| /******************************************************************************
 | |
|  *
 | |
|  *                  V4L2 M2M Interface
 | |
|  *
 | |
|  ******************************************************************************/
 | |
| int ff_v4l2_m2m_codec_reinit(V4L2m2mContext* s)
 | |
| {
 | |
|     int ret;
 | |
| 
 | |
|     av_log(s->avctx, AV_LOG_DEBUG, "reinit context\n");
 | |
| 
 | |
|     /* 1. streamoff */
 | |
|     ret = ff_v4l2_context_set_status(&s->capture, VIDIOC_STREAMOFF);
 | |
|     if (ret)
 | |
|         av_log(s->avctx, AV_LOG_ERROR, "capture VIDIOC_STREAMOFF\n");
 | |
| 
 | |
|     /* 2. unmap the capture buffers (v4l2 and ffmpeg):
 | |
|      *    we must wait for all references to be released before being allowed
 | |
|      *    to queue new buffers.
 | |
|      */
 | |
|     av_log(s->avctx, AV_LOG_DEBUG, "waiting for user to release AVBufferRefs\n");
 | |
|     if (atomic_load(&s->refcount))
 | |
|         while(sem_wait(&s->refsync) == -1 && errno == EINTR);
 | |
| 
 | |
|     ff_v4l2_context_release(&s->capture);
 | |
| 
 | |
|     /* 3. get the new capture format */
 | |
|     ret = ff_v4l2_context_get_format(&s->capture);
 | |
|     if (ret) {
 | |
|         av_log(s->avctx, AV_LOG_ERROR, "query the new capture format\n");
 | |
|         return ret;
 | |
|     }
 | |
| 
 | |
|     /* 4. set the capture format */
 | |
|     ret = ff_v4l2_context_set_format(&s->capture);
 | |
|     if (ret) {
 | |
|         av_log(s->avctx, AV_LOG_ERROR, "setting capture format\n");
 | |
|         return ret;
 | |
|     }
 | |
| 
 | |
|     /* 5. complete reinit */
 | |
|     s->draining = 0;
 | |
|     s->reinit = 0;
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int ff_v4l2_m2m_codec_full_reinit(V4L2m2mContext *s)
 | |
| {
 | |
|     void *log_ctx = s->avctx;
 | |
|     int ret;
 | |
| 
 | |
|     av_log(log_ctx, AV_LOG_DEBUG, "%s full reinit\n", s->devname);
 | |
| 
 | |
|     /* wait for pending buffer references */
 | |
|     if (atomic_load(&s->refcount))
 | |
|         while(sem_wait(&s->refsync) == -1 && errno == EINTR);
 | |
| 
 | |
|     ret = ff_v4l2_context_set_status(&s->output, VIDIOC_STREAMOFF);
 | |
|     if (ret) {
 | |
|         av_log(s->avctx, AV_LOG_ERROR, "output VIDIOC_STREAMOFF\n");
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     ret = ff_v4l2_context_set_status(&s->capture, VIDIOC_STREAMOFF);
 | |
|     if (ret) {
 | |
|             av_log(s->avctx, AV_LOG_ERROR, "capture VIDIOC_STREAMOFF\n");
 | |
|             goto error;
 | |
|     }
 | |
| 
 | |
|     /* release and unmmap the buffers */
 | |
|     ff_v4l2_context_release(&s->output);
 | |
|     ff_v4l2_context_release(&s->capture);
 | |
| 
 | |
|     /* start again now that we know the stream dimensions */
 | |
|     s->draining = 0;
 | |
|     s->reinit = 0;
 | |
| 
 | |
|     ret = ff_v4l2_context_get_format(&s->output);
 | |
|     if (ret) {
 | |
|         av_log(log_ctx, AV_LOG_DEBUG, "v4l2 output format not supported\n");
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     ret = ff_v4l2_context_get_format(&s->capture);
 | |
|     if (ret) {
 | |
|         av_log(log_ctx, AV_LOG_DEBUG, "v4l2 capture format not supported\n");
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     ret = ff_v4l2_context_set_format(&s->output);
 | |
|     if (ret) {
 | |
|         av_log(log_ctx, AV_LOG_ERROR, "can't set v4l2 output format\n");
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     ret = ff_v4l2_context_set_format(&s->capture);
 | |
|     if (ret) {
 | |
|         av_log(log_ctx, AV_LOG_ERROR, "can't to set v4l2 capture format\n");
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     ret = ff_v4l2_context_init(&s->output);
 | |
|     if (ret) {
 | |
|         av_log(log_ctx, AV_LOG_ERROR, "no v4l2 output context's buffers\n");
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     /* decoder's buffers need to be updated at a later stage */
 | |
|     if (!av_codec_is_decoder(s->avctx->codec)) {
 | |
|         ret = ff_v4l2_context_init(&s->capture);
 | |
|         if (ret) {
 | |
|             av_log(log_ctx, AV_LOG_ERROR, "no v4l2 capture context's buffers\n");
 | |
|             goto error;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| 
 | |
| error:
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static void v4l2_m2m_destroy_context(void *opaque, uint8_t *context)
 | |
| {
 | |
|     V4L2m2mContext *s = (V4L2m2mContext*)context;
 | |
| 
 | |
|     ff_v4l2_context_release(&s->capture);
 | |
|     sem_destroy(&s->refsync);
 | |
| 
 | |
|     close(s->fd);
 | |
| 
 | |
|     av_free(s);
 | |
| }
 | |
| 
 | |
| int ff_v4l2_m2m_codec_end(AVCodecContext *avctx)
 | |
| {
 | |
|     V4L2m2mPriv *priv = avctx->priv_data;
 | |
|     V4L2m2mContext* s = priv->context;
 | |
|     int ret;
 | |
| 
 | |
|     ret = ff_v4l2_context_set_status(&s->output, VIDIOC_STREAMOFF);
 | |
|     if (ret)
 | |
|             av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMOFF %s\n", s->output.name);
 | |
| 
 | |
|     ret = ff_v4l2_context_set_status(&s->capture, VIDIOC_STREAMOFF);
 | |
|     if (ret)
 | |
|         av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMOFF %s\n", s->capture.name);
 | |
| 
 | |
|     ff_v4l2_context_release(&s->output);
 | |
| 
 | |
|     s->self_ref = NULL;
 | |
|     av_buffer_unref(&priv->context_ref);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int ff_v4l2_m2m_codec_init(AVCodecContext *avctx)
 | |
| {
 | |
|     int ret = AVERROR(EINVAL);
 | |
|     struct dirent *entry;
 | |
|     char node[PATH_MAX];
 | |
|     DIR *dirp;
 | |
| 
 | |
|     V4L2m2mContext *s = ((V4L2m2mPriv*)avctx->priv_data)->context;
 | |
|     s->avctx = avctx;
 | |
| 
 | |
|     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(s->avctx, AV_LOG_DEBUG, "probing device %s\n", node);
 | |
|         strncpy(s->devname, node, strlen(node) + 1);
 | |
|         ret = v4l2_probe_driver(s);
 | |
|         if (!ret)
 | |
|                 break;
 | |
|     }
 | |
| 
 | |
|     closedir(dirp);
 | |
| 
 | |
|     if (ret) {
 | |
|         av_log(s->avctx, AV_LOG_ERROR, "Could not find a valid device\n");
 | |
|         memset(s->devname, 0, sizeof(s->devname));
 | |
| 
 | |
|         return ret;
 | |
|     }
 | |
| 
 | |
|     av_log(s->avctx, AV_LOG_INFO, "Using device %s\n", node);
 | |
| 
 | |
|     return v4l2_configure_contexts(s);
 | |
| }
 | |
| 
 | |
| int ff_v4l2_m2m_create_context(AVCodecContext *avctx, V4L2m2mContext **s)
 | |
| {
 | |
|     V4L2m2mPriv *priv = avctx->priv_data;
 | |
| 
 | |
|     *s = av_mallocz(sizeof(V4L2m2mContext));
 | |
|     if (!*s)
 | |
|         return AVERROR(ENOMEM);
 | |
| 
 | |
|     priv->context_ref = av_buffer_create((uint8_t *) *s, sizeof(V4L2m2mContext),
 | |
|                                          &v4l2_m2m_destroy_context, NULL, 0);
 | |
|     if (!priv->context_ref) {
 | |
|         av_freep(s);
 | |
|         return AVERROR(ENOMEM);
 | |
|     }
 | |
| 
 | |
|     /* assign the context */
 | |
|     priv->context = *s;
 | |
| 
 | |
|     /* populate it */
 | |
|     priv->context->capture.num_buffers = priv->num_capture_buffers;
 | |
|     priv->context->output.num_buffers  = priv->num_output_buffers;
 | |
|     priv->context->self_ref = priv->context_ref;
 | |
| 
 | |
|     return 0;
 | |
| }
 |