| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * V4L mem2mem | ||
| 3 | * | ||
| 4 | * Copyright (C) 2017 Alexis Ballier <aballier@gentoo.org> | ||
| 5 | * Copyright (C) 2017 Jorge Ramirez <jorge.ramirez-ortiz@linaro.org> | ||
| 6 | * | ||
| 7 | * This file is part of FFmpeg. | ||
| 8 | * | ||
| 9 | * FFmpeg is free software; you can redistribute it and/or | ||
| 10 | * modify it under the terms of the GNU Lesser General Public | ||
| 11 | * License as published by the Free Software Foundation; either | ||
| 12 | * version 2.1 of the License, or (at your option) any later version. | ||
| 13 | * | ||
| 14 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 17 | * Lesser General Public License for more details. | ||
| 18 | * | ||
| 19 | * You should have received a copy of the GNU Lesser General Public | ||
| 20 | * License along with FFmpeg; if not, write to the Free Software | ||
| 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 22 | */ | ||
| 23 | |||
| 24 | #include <linux/videodev2.h> | ||
| 25 | #include <sys/ioctl.h> | ||
| 26 | #include <sys/mman.h> | ||
| 27 | #include <unistd.h> | ||
| 28 | #include <dirent.h> | ||
| 29 | #include <fcntl.h> | ||
| 30 | #include "libavcodec/avcodec.h" | ||
| 31 | #include "libavutil/mem.h" | ||
| 32 | #include "libavutil/pixdesc.h" | ||
| 33 | #include "libavutil/imgutils.h" | ||
| 34 | #include "libavutil/pixfmt.h" | ||
| 35 | #include "libavutil/refstruct.h" | ||
| 36 | #include "v4l2_context.h" | ||
| 37 | #include "v4l2_fmt.h" | ||
| 38 | #include "v4l2_m2m.h" | ||
| 39 | |||
| 40 | ✗ | static inline int v4l2_splane_video(struct v4l2_capability *cap) | |
| 41 | { | ||
| 42 | ✗ | if (cap->capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT) && | |
| 43 | ✗ | cap->capabilities & V4L2_CAP_STREAMING) | |
| 44 | ✗ | return 1; | |
| 45 | |||
| 46 | ✗ | if (cap->capabilities & V4L2_CAP_VIDEO_M2M) | |
| 47 | ✗ | return 1; | |
| 48 | |||
| 49 | ✗ | return 0; | |
| 50 | } | ||
| 51 | |||
| 52 | ✗ | static inline int v4l2_mplane_video(struct v4l2_capability *cap) | |
| 53 | { | ||
| 54 | ✗ | if (cap->capabilities & (V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE) && | |
| 55 | ✗ | cap->capabilities & V4L2_CAP_STREAMING) | |
| 56 | ✗ | return 1; | |
| 57 | |||
| 58 | ✗ | if (cap->capabilities & V4L2_CAP_VIDEO_M2M_MPLANE) | |
| 59 | ✗ | return 1; | |
| 60 | |||
| 61 | ✗ | return 0; | |
| 62 | } | ||
| 63 | |||
| 64 | ✗ | static int v4l2_prepare_contexts(V4L2m2mContext *s, int probe) | |
| 65 | { | ||
| 66 | struct v4l2_capability cap; | ||
| 67 | ✗ | void *log_ctx = s->avctx; | |
| 68 | int ret; | ||
| 69 | |||
| 70 | ✗ | s->capture.done = s->output.done = 0; | |
| 71 | ✗ | s->capture.name = "capture"; | |
| 72 | ✗ | s->output.name = "output"; | |
| 73 | ✗ | atomic_init(&s->refcount, 0); | |
| 74 | ✗ | sem_init(&s->refsync, 0, 0); | |
| 75 | |||
| 76 | ✗ | memset(&cap, 0, sizeof(cap)); | |
| 77 | ✗ | ret = ioctl(s->fd, VIDIOC_QUERYCAP, &cap); | |
| 78 | ✗ | if (ret < 0) | |
| 79 | ✗ | return ret; | |
| 80 | |||
| 81 | ✗ | av_log(log_ctx, probe ? AV_LOG_DEBUG : AV_LOG_INFO, | |
| 82 | "driver '%s' on card '%s' in %s mode\n", cap.driver, cap.card, | ||
| 83 | ✗ | v4l2_mplane_video(&cap) ? "mplane" : | |
| 84 | ✗ | v4l2_splane_video(&cap) ? "splane" : "unknown"); | |
| 85 | |||
| 86 | ✗ | if (v4l2_mplane_video(&cap)) { | |
| 87 | ✗ | s->capture.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | |
| 88 | ✗ | s->output.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | |
| 89 | ✗ | return 0; | |
| 90 | } | ||
| 91 | |||
| 92 | ✗ | if (v4l2_splane_video(&cap)) { | |
| 93 | ✗ | s->capture.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| 94 | ✗ | s->output.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; | |
| 95 | ✗ | return 0; | |
| 96 | } | ||
| 97 | |||
| 98 | ✗ | return AVERROR(EINVAL); | |
| 99 | } | ||
| 100 | |||
| 101 | ✗ | static int v4l2_probe_driver(V4L2m2mContext *s) | |
| 102 | { | ||
| 103 | ✗ | void *log_ctx = s->avctx; | |
| 104 | int ret; | ||
| 105 | |||
| 106 | ✗ | s->fd = open(s->devname, O_RDWR | O_NONBLOCK, 0); | |
| 107 | ✗ | if (s->fd < 0) | |
| 108 | ✗ | return AVERROR(errno); | |
| 109 | |||
| 110 | ✗ | ret = v4l2_prepare_contexts(s, 1); | |
| 111 | ✗ | if (ret < 0) | |
| 112 | ✗ | goto done; | |
| 113 | |||
| 114 | ✗ | ret = ff_v4l2_context_get_format(&s->output, 1); | |
| 115 | ✗ | if (ret) { | |
| 116 | ✗ | av_log(log_ctx, AV_LOG_DEBUG, "v4l2 output format not supported\n"); | |
| 117 | ✗ | goto done; | |
| 118 | } | ||
| 119 | |||
| 120 | ✗ | ret = ff_v4l2_context_get_format(&s->capture, 1); | |
| 121 | ✗ | if (ret) { | |
| 122 | ✗ | av_log(log_ctx, AV_LOG_DEBUG, "v4l2 capture format not supported\n"); | |
| 123 | ✗ | goto done; | |
| 124 | } | ||
| 125 | |||
| 126 | ✗ | done: | |
| 127 | ✗ | if (close(s->fd) < 0) { | |
| 128 | ✗ | ret = AVERROR(errno); | |
| 129 | ✗ | av_log(log_ctx, AV_LOG_ERROR, "failure closing %s (%s)\n", s->devname, av_err2str(AVERROR(errno))); | |
| 130 | } | ||
| 131 | |||
| 132 | ✗ | s->fd = -1; | |
| 133 | |||
| 134 | ✗ | return ret; | |
| 135 | } | ||
| 136 | |||
| 137 | ✗ | static av_cold int v4l2_configure_contexts(V4L2m2mContext *s) | |
| 138 | { | ||
| 139 | ✗ | void *log_ctx = s->avctx; | |
| 140 | int ret; | ||
| 141 | struct v4l2_format ofmt, cfmt; | ||
| 142 | |||
| 143 | ✗ | s->fd = open(s->devname, O_RDWR | O_NONBLOCK, 0); | |
| 144 | ✗ | if (s->fd < 0) | |
| 145 | ✗ | return AVERROR(errno); | |
| 146 | |||
| 147 | ✗ | ret = v4l2_prepare_contexts(s, 0); | |
| 148 | ✗ | if (ret < 0) | |
| 149 | ✗ | goto error; | |
| 150 | |||
| 151 | ✗ | ofmt = s->output.format; | |
| 152 | ✗ | cfmt = s->capture.format; | |
| 153 | ✗ | av_log(log_ctx, AV_LOG_INFO, "requesting formats: output=%s/%s capture=%s/%s\n", | |
| 154 | ✗ | av_fourcc2str(V4L2_TYPE_IS_MULTIPLANAR(ofmt.type) ? | |
| 155 | ofmt.fmt.pix_mp.pixelformat : | ||
| 156 | ofmt.fmt.pix.pixelformat), | ||
| 157 | ✗ | av_get_pix_fmt_name(s->output.av_pix_fmt) ?: "none", | |
| 158 | ✗ | av_fourcc2str(V4L2_TYPE_IS_MULTIPLANAR(cfmt.type) ? | |
| 159 | cfmt.fmt.pix_mp.pixelformat : | ||
| 160 | cfmt.fmt.pix.pixelformat), | ||
| 161 | ✗ | av_get_pix_fmt_name(s->capture.av_pix_fmt) ?: "none"); | |
| 162 | |||
| 163 | ✗ | ret = ff_v4l2_context_set_format(&s->output); | |
| 164 | ✗ | if (ret) { | |
| 165 | ✗ | av_log(log_ctx, AV_LOG_ERROR, "can't set v4l2 output format\n"); | |
| 166 | ✗ | goto error; | |
| 167 | } | ||
| 168 | |||
| 169 | ✗ | ret = ff_v4l2_context_set_format(&s->capture); | |
| 170 | ✗ | if (ret) { | |
| 171 | ✗ | av_log(log_ctx, AV_LOG_ERROR, "can't to set v4l2 capture format\n"); | |
| 172 | ✗ | goto error; | |
| 173 | } | ||
| 174 | |||
| 175 | ✗ | ret = ff_v4l2_context_init(&s->output); | |
| 176 | ✗ | if (ret) { | |
| 177 | ✗ | av_log(log_ctx, AV_LOG_ERROR, "no v4l2 output context's buffers\n"); | |
| 178 | ✗ | goto error; | |
| 179 | } | ||
| 180 | |||
| 181 | /* decoder's buffers need to be updated at a later stage */ | ||
| 182 | ✗ | if (s->avctx && !av_codec_is_decoder(s->avctx->codec)) { | |
| 183 | ✗ | ret = ff_v4l2_context_init(&s->capture); | |
| 184 | ✗ | if (ret) { | |
| 185 | ✗ | av_log(log_ctx, AV_LOG_ERROR, "no v4l2 capture context's buffers\n"); | |
| 186 | ✗ | goto error; | |
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | ✗ | return 0; | |
| 191 | |||
| 192 | ✗ | error: | |
| 193 | ✗ | if (close(s->fd) < 0) { | |
| 194 | ✗ | ret = AVERROR(errno); | |
| 195 | ✗ | av_log(log_ctx, AV_LOG_ERROR, "error closing %s (%s)\n", | |
| 196 | ✗ | s->devname, av_err2str(AVERROR(errno))); | |
| 197 | } | ||
| 198 | ✗ | s->fd = -1; | |
| 199 | |||
| 200 | ✗ | return ret; | |
| 201 | } | ||
| 202 | |||
| 203 | /****************************************************************************** | ||
| 204 | * | ||
| 205 | * V4L2 M2M Interface | ||
| 206 | * | ||
| 207 | ******************************************************************************/ | ||
| 208 | ✗ | int ff_v4l2_m2m_codec_reinit(V4L2m2mContext *s) | |
| 209 | { | ||
| 210 | ✗ | void *log_ctx = s->avctx; | |
| 211 | int ret; | ||
| 212 | |||
| 213 | ✗ | av_log(log_ctx, AV_LOG_DEBUG, "reinit context\n"); | |
| 214 | |||
| 215 | /* 1. streamoff */ | ||
| 216 | ✗ | ret = ff_v4l2_context_set_status(&s->capture, VIDIOC_STREAMOFF); | |
| 217 | ✗ | if (ret) | |
| 218 | ✗ | av_log(log_ctx, AV_LOG_ERROR, "capture VIDIOC_STREAMOFF\n"); | |
| 219 | |||
| 220 | /* 2. unmap the capture buffers (v4l2 and ffmpeg): | ||
| 221 | * we must wait for all references to be released before being allowed | ||
| 222 | * to queue new buffers. | ||
| 223 | */ | ||
| 224 | ✗ | av_log(log_ctx, AV_LOG_DEBUG, "waiting for user to release AVBufferRefs\n"); | |
| 225 | ✗ | if (atomic_load(&s->refcount)) | |
| 226 | ✗ | while(sem_wait(&s->refsync) == -1 && errno == EINTR); | |
| 227 | |||
| 228 | ✗ | ff_v4l2_context_release(&s->capture); | |
| 229 | |||
| 230 | /* 3. get the new capture format */ | ||
| 231 | ✗ | ret = ff_v4l2_context_get_format(&s->capture, 0); | |
| 232 | ✗ | if (ret) { | |
| 233 | ✗ | av_log(log_ctx, AV_LOG_ERROR, "query the new capture format\n"); | |
| 234 | ✗ | return ret; | |
| 235 | } | ||
| 236 | |||
| 237 | /* 4. set the capture format */ | ||
| 238 | ✗ | ret = ff_v4l2_context_set_format(&s->capture); | |
| 239 | ✗ | if (ret) { | |
| 240 | ✗ | av_log(log_ctx, AV_LOG_ERROR, "setting capture format\n"); | |
| 241 | ✗ | return ret; | |
| 242 | } | ||
| 243 | |||
| 244 | /* 5. complete reinit */ | ||
| 245 | ✗ | s->draining = 0; | |
| 246 | ✗ | s->reinit = 0; | |
| 247 | |||
| 248 | ✗ | return 0; | |
| 249 | } | ||
| 250 | |||
| 251 | ✗ | static void v4l2_m2m_destroy_context(AVRefStructOpaque unused, void *context) | |
| 252 | { | ||
| 253 | ✗ | V4L2m2mContext *s = context; | |
| 254 | |||
| 255 | ✗ | ff_v4l2_context_release(&s->capture); | |
| 256 | ✗ | sem_destroy(&s->refsync); | |
| 257 | |||
| 258 | ✗ | if (s->fd >= 0) | |
| 259 | ✗ | close(s->fd); | |
| 260 | ✗ | av_frame_free(&s->frame); | |
| 261 | ✗ | av_packet_unref(&s->buf_pkt); | |
| 262 | ✗ | } | |
| 263 | |||
| 264 | ✗ | av_cold int ff_v4l2_m2m_codec_end(V4L2m2mPriv *priv) | |
| 265 | { | ||
| 266 | ✗ | V4L2m2mContext *s = priv->context; | |
| 267 | int ret; | ||
| 268 | |||
| 269 | ✗ | if (!s) | |
| 270 | ✗ | return 0; | |
| 271 | |||
| 272 | ✗ | if (s->fd >= 0) { | |
| 273 | ✗ | ret = ff_v4l2_context_set_status(&s->output, VIDIOC_STREAMOFF); | |
| 274 | ✗ | if (ret) | |
| 275 | ✗ | av_log(s->avctx, AV_LOG_ERROR, "VIDIOC_STREAMOFF %s\n", s->output.name); | |
| 276 | |||
| 277 | ✗ | ret = ff_v4l2_context_set_status(&s->capture, VIDIOC_STREAMOFF); | |
| 278 | ✗ | if (ret) | |
| 279 | ✗ | av_log(s->avctx, AV_LOG_ERROR, "VIDIOC_STREAMOFF %s\n", s->capture.name); | |
| 280 | } | ||
| 281 | |||
| 282 | ✗ | ff_v4l2_context_release(&s->output); | |
| 283 | |||
| 284 | ✗ | s->self_ref = NULL; | |
| 285 | ✗ | av_refstruct_unref(&priv->context); | |
| 286 | |||
| 287 | ✗ | return 0; | |
| 288 | } | ||
| 289 | |||
| 290 | ✗ | av_cold int ff_v4l2_m2m_codec_init(V4L2m2mPriv *priv) | |
| 291 | { | ||
| 292 | ✗ | int ret = AVERROR(EINVAL); | |
| 293 | struct dirent *entry; | ||
| 294 | DIR *dirp; | ||
| 295 | |||
| 296 | ✗ | V4L2m2mContext *s = priv->context; | |
| 297 | |||
| 298 | ✗ | dirp = opendir("/dev"); | |
| 299 | ✗ | if (!dirp) | |
| 300 | ✗ | return AVERROR(errno); | |
| 301 | |||
| 302 | ✗ | for (entry = readdir(dirp); entry; entry = readdir(dirp)) { | |
| 303 | |||
| 304 | ✗ | if (strncmp(entry->d_name, "video", 5)) | |
| 305 | ✗ | continue; | |
| 306 | |||
| 307 | ✗ | snprintf(s->devname, sizeof(s->devname), "/dev/%s", entry->d_name); | |
| 308 | ✗ | av_log(s->avctx, AV_LOG_DEBUG, "probing device %s\n", s->devname); | |
| 309 | ✗ | ret = v4l2_probe_driver(s); | |
| 310 | ✗ | if (!ret) | |
| 311 | ✗ | break; | |
| 312 | } | ||
| 313 | |||
| 314 | ✗ | closedir(dirp); | |
| 315 | |||
| 316 | ✗ | if (ret) { | |
| 317 | ✗ | av_log(s->avctx, AV_LOG_ERROR, "Could not find a valid device\n"); | |
| 318 | ✗ | memset(s->devname, 0, sizeof(s->devname)); | |
| 319 | |||
| 320 | ✗ | return ret; | |
| 321 | } | ||
| 322 | |||
| 323 | ✗ | av_log(s->avctx, AV_LOG_INFO, "Using device %s\n", s->devname); | |
| 324 | |||
| 325 | ✗ | return v4l2_configure_contexts(s); | |
| 326 | } | ||
| 327 | |||
| 328 | ✗ | int ff_v4l2_m2m_create_context(V4L2m2mPriv *priv, V4L2m2mContext **s) | |
| 329 | { | ||
| 330 | ✗ | *s = av_refstruct_alloc_ext(sizeof(**s), 0, NULL, | |
| 331 | &v4l2_m2m_destroy_context); | ||
| 332 | ✗ | if (!*s) | |
| 333 | ✗ | return AVERROR(ENOMEM); | |
| 334 | |||
| 335 | /* assign the context */ | ||
| 336 | ✗ | priv->context = *s; | |
| 337 | ✗ | (*s)->priv = priv; | |
| 338 | |||
| 339 | /* populate it */ | ||
| 340 | ✗ | priv->context->capture.num_buffers = priv->num_capture_buffers; | |
| 341 | ✗ | priv->context->output.num_buffers = priv->num_output_buffers; | |
| 342 | ✗ | priv->context->self_ref = priv->context; | |
| 343 | ✗ | priv->context->fd = -1; | |
| 344 | |||
| 345 | ✗ | priv->context->frame = av_frame_alloc(); | |
| 346 | ✗ | if (!priv->context->frame) { | |
| 347 | ✗ | av_refstruct_unref(&priv->context); | |
| 348 | ✗ | *s = NULL; /* freed when unreferencing context */ | |
| 349 | ✗ | return AVERROR(ENOMEM); | |
| 350 | } | ||
| 351 | |||
| 352 | ✗ | return 0; | |
| 353 | } | ||
| 354 |