static int v4l2_receive_packet(AVCodecContext *avctx, AVPacket *avpkt) { V4L2m2mContext *s = avctx->priv_data; V4L2Context *const capture = &s->capture; V4L2Context *const output = &s->output; int ret; if (s->draining) goto dequeue; if (!output->streamon) { ret = ff_v4l2_context_set_status(output, VIDIOC_STREAMON); if (ret) { av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMOFF failed on output context\n"); return ret; } } if (!capture->streamon) { ret = ff_v4l2_context_set_status(capture, VIDIOC_STREAMON); if (ret) { av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMON failed on capture context\n"); return ret; } } dequeue: return ff_v4l2_context_dequeue_packet(capture, avpkt); }
static int v4l2_stop_decode(V4L2Context *ctx) { struct v4l2_decoder_cmd cmd = { .cmd = V4L2_DEC_CMD_STOP, .flags = 0, }; int ret; ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_DECODER_CMD, &cmd); if (ret) { /* DECODER_CMD is optional */ if (errno == ENOTTY) return ff_v4l2_context_set_status(ctx, VIDIOC_STREAMOFF); } return 0; } static int v4l2_stop_encode(V4L2Context *ctx) { struct v4l2_encoder_cmd cmd = { .cmd = V4L2_ENC_CMD_STOP, .flags = 0, }; int ret; ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_ENCODER_CMD, &cmd); if (ret) { /* ENCODER_CMD is optional */ if (errno == ENOTTY) return ff_v4l2_context_set_status(ctx, VIDIOC_STREAMOFF); } return 0; } static V4L2Buffer* v4l2_dequeue_v4l2buf(V4L2Context *ctx, int timeout) { struct v4l2_plane planes[VIDEO_MAX_PLANES]; struct v4l2_buffer buf = { 0 }; V4L2Buffer* avbuf = NULL; struct pollfd pfd = { .events = POLLIN | POLLRDNORM | POLLPRI | POLLOUT | POLLWRNORM, /* default blocking capture */ .fd = ctx_to_m2mctx(ctx)->fd, }; int i, ret; /* if we are draining and there are no more capture buffers queued in the driver we are done */ if (!V4L2_TYPE_IS_OUTPUT(ctx->type) && ctx_to_m2mctx(ctx)->draining) { for (i = 0; i < ctx->num_buffers; i++) { if (ctx->buffers[i].status == V4L2BUF_IN_DRIVER) goto start; } ctx->done = 1; return NULL; } start: if (V4L2_TYPE_IS_OUTPUT(ctx->type)) pfd.events = POLLOUT | POLLWRNORM; else { /* no need to listen to requests for more input while draining */ if (ctx_to_m2mctx(ctx)->draining) pfd.events = POLLIN | POLLRDNORM | POLLPRI; } for (;;) { ret = poll(&pfd, 1, timeout); if (ret > 0) break; if (errno == EINTR) continue; return NULL; } /* 0. handle errors */ if (pfd.revents & POLLERR) { /* if we are trying to get free buffers but none have been queued yet no need to raise a warning */ if (timeout == 0) { for (i = 0; i < ctx->num_buffers; i++) { if (ctx->buffers[i].status != V4L2BUF_AVAILABLE) av_log(logger(ctx), AV_LOG_WARNING, "%s POLLERR\n", ctx->name); } } else av_log(logger(ctx), AV_LOG_WARNING, "%s POLLERR\n", ctx->name); return NULL; } /* 1. handle resolution changes */ if (pfd.revents & POLLPRI) { ret = v4l2_handle_event(ctx); if (ret < 0) { /* if re-init failed, abort */ ctx->done = 1; return NULL; } if (ret) { /* if re-init was successful drop the buffer (if there was one) * since we had to reconfigure capture (unmap all buffers) */ return NULL; } } /* 2. dequeue the buffer */ if (pfd.revents & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM)) { if (!V4L2_TYPE_IS_OUTPUT(ctx->type)) { /* there is a capture buffer ready */ if (pfd.revents & (POLLIN | POLLRDNORM)) goto dequeue; /* the driver is ready to accept more input; instead of waiting for the capture * buffer to complete we return NULL so input can proceed (we are single threaded) */ if (pfd.revents & (POLLOUT | POLLWRNORM)) return NULL; } dequeue: memset(&buf, 0, sizeof(buf)); buf.memory = V4L2_MEMORY_MMAP; buf.type = ctx->type; if (V4L2_TYPE_IS_MULTIPLANAR(ctx->type)) { memset(planes, 0, sizeof(planes)); buf.length = VIDEO_MAX_PLANES; buf.m.planes = planes; } ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_DQBUF, &buf); if (ret) { if (errno != EAGAIN) { ctx->done = 1; if (errno != EPIPE) av_log(logger(ctx), AV_LOG_DEBUG, "%s VIDIOC_DQBUF, errno (%s)\n", ctx->name, av_err2str(AVERROR(errno))); } return NULL; } avbuf = &ctx->buffers[buf.index]; avbuf->status = V4L2BUF_AVAILABLE; avbuf->buf = buf; if (V4L2_TYPE_IS_MULTIPLANAR(ctx->type)) { memcpy(avbuf->planes, planes, sizeof(planes)); avbuf->buf.m.planes = avbuf->planes; } return avbuf; } return NULL; } static V4L2Buffer* v4l2_getfree_v4l2buf(V4L2Context *ctx) { int timeout = 0; /* return when no more buffers to dequeue */ int i; /* get back as many output buffers as possible */ if (V4L2_TYPE_IS_OUTPUT(ctx->type)) { do { } while (v4l2_dequeue_v4l2buf(ctx, timeout)); } for (i = 0; i < ctx->num_buffers; i++) { if (ctx->buffers[i].status == V4L2BUF_AVAILABLE) return &ctx->buffers[i]; } return NULL; } static int v4l2_release_buffers(V4L2Context* ctx) { struct v4l2_requestbuffers req = { .memory = V4L2_MEMORY_MMAP, .type = ctx->type, .count = 0, /* 0 -> unmaps buffers from the driver */ }; int i, j; for (i = 0; i < ctx->num_buffers; i++) { V4L2Buffer *buffer = &ctx->buffers[i]; for (j = 0; j < buffer->num_planes; j++) { struct V4L2Plane_info *p = &buffer->plane_info[j]; if (p->mm_addr && p->length) if (munmap(p->mm_addr, p->length) < 0) av_log(logger(ctx), AV_LOG_ERROR, "%s unmap plane (%s))\n", ctx->name, av_err2str(AVERROR(errno))); } } return ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_REQBUFS, &req); } static inline int v4l2_try_raw_format(V4L2Context* ctx, enum AVPixelFormat pixfmt) { struct v4l2_format *fmt = &ctx->format; uint32_t v4l2_fmt; int ret; v4l2_fmt = ff_v4l2_format_avfmt_to_v4l2(pixfmt); if (!v4l2_fmt) return AVERROR(EINVAL); if (V4L2_TYPE_IS_MULTIPLANAR(ctx->type)) fmt->fmt.pix_mp.pixelformat = v4l2_fmt; else fmt->fmt.pix.pixelformat = v4l2_fmt; fmt->type = ctx->type; ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_TRY_FMT, fmt); if (ret) return AVERROR(EINVAL); return 0; } static int v4l2_get_raw_format(V4L2Context* ctx, enum AVPixelFormat *p) { enum AVPixelFormat pixfmt = ctx->av_pix_fmt; struct v4l2_fmtdesc fdesc; int ret; memset(&fdesc, 0, sizeof(fdesc)); fdesc.type = ctx->type; if (pixfmt != AV_PIX_FMT_NONE) { ret = v4l2_try_raw_format(ctx, pixfmt); if (!ret) return 0; } for (;;) { ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_ENUM_FMT, &fdesc); if (ret) return AVERROR(EINVAL); pixfmt = ff_v4l2_format_v4l2_to_avfmt(fdesc.pixelformat, AV_CODEC_ID_RAWVIDEO); ret = v4l2_try_raw_format(ctx, pixfmt); if (ret){ fdesc.index++; continue; } *p = pixfmt; return 0; } return AVERROR(EINVAL); } static int v4l2_get_coded_format(V4L2Context* ctx, uint32_t *p) { struct v4l2_fmtdesc fdesc; uint32_t v4l2_fmt; int ret; /* translate to a valid v4l2 format */ v4l2_fmt = ff_v4l2_format_avcodec_to_v4l2(ctx->av_codec_id); if (!v4l2_fmt) return AVERROR(EINVAL); /* check if the driver supports this format */ memset(&fdesc, 0, sizeof(fdesc)); fdesc.type = ctx->type; for (;;) { ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_ENUM_FMT, &fdesc); if (ret) return AVERROR(EINVAL); if (fdesc.pixelformat == v4l2_fmt) break; fdesc.index++; } *p = v4l2_fmt; return 0; } /***************************************************************************** * * V4L2 Context Interface * *****************************************************************************/ int ff_v4l2_context_set_status(V4L2Context* ctx, uint32_t cmd) { int type = ctx->type; int ret; ret = ioctl(ctx_to_m2mctx(ctx)->fd, cmd, &type); if (ret < 0) return AVERROR(errno); ctx->streamon = (cmd == VIDIOC_STREAMON); return 0; } int ff_v4l2_context_enqueue_frame(V4L2Context* ctx, const AVFrame* frame) { V4L2m2mContext *s = ctx_to_m2mctx(ctx); V4L2Buffer* avbuf; int ret; if (!frame) { ret = v4l2_stop_encode(ctx); if (ret) av_log(logger(ctx), AV_LOG_ERROR, "%s stop_encode\n", ctx->name); s->draining= 1; return 0; } avbuf = v4l2_getfree_v4l2buf(ctx); if (!avbuf) return AVERROR(ENOMEM); ret = ff_v4l2_buffer_avframe_to_buf(frame, avbuf); if (ret) return ret; return ff_v4l2_buffer_enqueue(avbuf); } int ff_v4l2_context_enqueue_packet(V4L2Context* ctx, const AVPacket* pkt) { V4L2m2mContext *s = ctx_to_m2mctx(ctx); V4L2Buffer* avbuf; int ret; if (!pkt->size) { ret = v4l2_stop_decode(ctx); if (ret) av_log(logger(ctx), AV_LOG_ERROR, "%s stop_decode\n", ctx->name); s->draining = 1; return 0; } avbuf = v4l2_getfree_v4l2buf(ctx); if (!avbuf) return AVERROR(ENOMEM); ret = ff_v4l2_buffer_avpkt_to_buf(pkt, avbuf); if (ret) return ret; return ff_v4l2_buffer_enqueue(avbuf); }