android_external_ffmpeg/libavcodec/v4l2_context.c
2025-06-26 23:59:17 -05:00

1356 lines
40 KiB
C

/*
* V4L2 context helper functions.
*
* 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 <fcntl.h>
#include <poll.h>
#include "libavutil/mem.h"
#include "libavutil/avassert.h"
#include "libavutil/pixdesc.h"
#include "libavcodec/avcodec.h"
#include "decode.h"
#include "v4l2_buffers.h"
#include "v4l2_fmt.h"
#include "v4l2_m2m.h"
#include "weak_link.h"
struct v4l2_format_update {
uint32_t v4l2_fmt;
int update_v4l2;
enum AVPixelFormat av_fmt;
int update_avfmt;
};
static inline int64_t track_to_pts(AVCodecContext *avctx, unsigned int n)
{
return (int64_t)n;
}
static inline unsigned int pts_to_track(AVCodecContext *avctx, const int64_t pts)
{
return (unsigned int)pts;
}
// FFmpeg requires us to propagate a number of vars from the coded pkt into
// the decoded frame. The only thing that tracks like that in V4L2 stateful
// is timestamp. PTS maps to timestamp for this decode. FFmpeg makes no
// guarantees about PTS being unique or specified for every frame so replace
// the supplied PTS with a simple incrementing number and keep a circular
// buffer of all the things we want preserved (including the original PTS)
// indexed by the tracking no.
static int64_t
xlat_pts_pkt_in(AVCodecContext *const avctx, xlat_track_t *const x, const AVPacket *const avpkt)
{
int64_t track_pts;
// Avoid 0
if (++x->track_no == 0)
x->track_no = 1;
track_pts = track_to_pts(avctx, x->track_no);
av_log(avctx, AV_LOG_TRACE, "In pkt PTS=%" PRId64 ", DTS=%" PRId64 ", track=%" PRId64 ", n=%u\n", avpkt->pts, avpkt->dts, track_pts, x->track_no);
x->track_els[x->track_no % FF_V4L2_M2M_TRACK_SIZE] = (V4L2m2mTrackEl){
.discard = 0,
.pending = 1,
.pkt_size = avpkt->size,
.pts = avpkt->pts,
.dts = avpkt->dts,
.pkt_pos = avpkt->pos,
.duration = avpkt->duration,
.track_pts = track_pts
};
return track_pts;
}
static int64_t
xlat_pts_frame_in(AVCodecContext *const avctx, xlat_track_t *const x, const AVFrame *const frame)
{
int64_t track_pts;
// Avoid 0
if (++x->track_no == 0)
x->track_no = 1;
track_pts = track_to_pts(avctx, x->track_no);
av_log(avctx, AV_LOG_TRACE, "In frame PTS=%" PRId64 ", track=%" PRId64 ", n=%u\n", frame->pts, track_pts, x->track_no);
x->track_els[x->track_no % FF_V4L2_M2M_TRACK_SIZE] = (V4L2m2mTrackEl){
.discard = 0,
.pending = 1,
.pts = frame->pts,
.dts = AV_NOPTS_VALUE,
.duration = frame->duration,
.track_pts = track_pts
};
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
x->track_els[x->track_no % FF_V4L2_M2M_TRACK_SIZE].pkt_pos = frame->pkt_pos;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
return track_pts;
}
// Returns -1 if we should discard the frame
static int
xlat_pts_frame_out(AVCodecContext *const avctx,
xlat_track_t * const x,
AVFrame *const frame)
{
unsigned int n = pts_to_track(avctx, frame->pts) % FF_V4L2_M2M_TRACK_SIZE;
V4L2m2mTrackEl *const t = x->track_els + n;
if (frame->pts == AV_NOPTS_VALUE || frame->pts != t->track_pts)
{
av_log(avctx, frame->pts == AV_NOPTS_VALUE ? AV_LOG_DEBUG : AV_LOG_WARNING,
"Frame tracking failure: pts=%" PRId64 ", track[%d]=%" PRId64 "\n", frame->pts, n, t->track_pts);
frame->pts = AV_NOPTS_VALUE;
frame->pkt_dts = AV_NOPTS_VALUE;
frame->duration = 0;
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
frame->pkt_size = -1;
frame->pkt_pos = -1;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
}
else if (!t->discard)
{
frame->pts = t->pending ? t->pts : AV_NOPTS_VALUE;
frame->pkt_dts = t->dts;
frame->duration = t->duration;
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
frame->pkt_pos = t->pkt_pos;
frame->pkt_size = t->pkt_size;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
if (frame->pts != AV_NOPTS_VALUE)
x->last_pts = frame->pts;
t->pending = 0;
}
else
{
av_log(avctx, AV_LOG_DEBUG, "Discard frame (flushed): pts=%" PRId64 ", track[%d]=%" PRId64 "\n", frame->pts, n, t->track_pts);
return -1;
}
av_log(avctx, AV_LOG_TRACE, "Out frame PTS=%" PRId64 "/%"PRId64", DTS=%" PRId64 ", track=%"PRId64", n=%d\n",
frame->pts, frame->best_effort_timestamp, frame->pkt_dts, t->track_pts, n);
return 0;
}
// Returns -1 if we should discard the frame
static int
xlat_pts_pkt_out(AVCodecContext *const avctx,
xlat_track_t * const x,
AVPacket *const pkt)
{
unsigned int n = pts_to_track(avctx, pkt->pts) % FF_V4L2_M2M_TRACK_SIZE;
V4L2m2mTrackEl *const t = x->track_els + n;
if (pkt->pts == AV_NOPTS_VALUE || pkt->pts != t->track_pts)
{
av_log(avctx, pkt->pts == AV_NOPTS_VALUE ? AV_LOG_DEBUG : AV_LOG_WARNING,
"Pkt tracking failure: pts=%" PRId64 ", track[%d]=%" PRId64 "\n", pkt->pts, n, t->track_pts);
pkt->pts = AV_NOPTS_VALUE;
}
else if (!t->discard)
{
pkt->pts = t->pending ? t->pts : AV_NOPTS_VALUE;
if (pkt->pts != AV_NOPTS_VALUE)
x->last_pts = pkt->pts;
t->pending = 0;
}
else
{
av_log(avctx, AV_LOG_DEBUG, "Discard packet (flushed): pts=%" PRId64 ", track[%d]=%" PRId64 "\n", pkt->pts, n, t->track_pts);
return -1;
}
// * Would like something much better than this...xlat(offset + out_count)?
pkt->dts = pkt->pts;
av_log(avctx, AV_LOG_TRACE, "Out pkt PTS=%" PRId64 ", track=%"PRId64", n=%d\n",
pkt->pts, t->track_pts, n);
return 0;
}
static inline V4L2m2mContext *ctx_to_m2mctx(const V4L2Context *ctx)
{
return V4L2_TYPE_IS_OUTPUT(ctx->type) ?
container_of(ctx, V4L2m2mContext, output) :
container_of(ctx, V4L2m2mContext, capture);
}
static inline AVCodecContext *logger(const V4L2Context *ctx)
{
return ctx_to_m2mctx(ctx)->avctx;
}
static AVRational v4l2_get_sar(V4L2Context *ctx)
{
struct AVRational sar = { 0, 1 };
struct v4l2_cropcap cropcap;
int ret;
memset(&cropcap, 0, sizeof(cropcap));
cropcap.type = ctx->type;
ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_CROPCAP, &cropcap);
if (ret)
return sar;
sar.num = cropcap.pixelaspect.numerator;
sar.den = cropcap.pixelaspect.denominator;
return sar;
}
static inline int ctx_buffers_alloced(const V4L2Context * const ctx)
{
return ctx->bufrefs != NULL;
}
// Width/Height changed or we don't have an alloc in the first place?
static int ctx_resolution_changed(const V4L2Context *ctx, const struct v4l2_format *fmt2)
{
const struct v4l2_format *fmt1 = &ctx->format;
int ret = !ctx_buffers_alloced(ctx) ||
(V4L2_TYPE_IS_MULTIPLANAR(ctx->type) ?
fmt1->fmt.pix_mp.width != fmt2->fmt.pix_mp.width ||
fmt1->fmt.pix_mp.height != fmt2->fmt.pix_mp.height
:
fmt1->fmt.pix.width != fmt2->fmt.pix.width ||
fmt1->fmt.pix.height != fmt2->fmt.pix.height);
if (ret)
av_log(logger(ctx), AV_LOG_DEBUG, "V4L2 %s changed: alloc=%d (%dx%d) -> (%dx%d)\n",
ctx->name,
ctx_buffers_alloced(ctx),
ff_v4l2_get_format_width(fmt1), ff_v4l2_get_format_height(fmt1),
ff_v4l2_get_format_width(fmt2), ff_v4l2_get_format_height(fmt2));
return ret;
}
static inline int v4l2_type_supported(V4L2Context *ctx)
{
return ctx->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ||
ctx->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ||
ctx->type == V4L2_BUF_TYPE_VIDEO_CAPTURE ||
ctx->type == V4L2_BUF_TYPE_VIDEO_OUTPUT;
}
static inline int v4l2_get_framesize_compressed(V4L2Context* ctx, int width, int height)
{
V4L2m2mContext *s = ctx_to_m2mctx(ctx);
const int SZ_4K = 0x1000;
int size;
if (s->avctx && av_codec_is_decoder(s->avctx->codec))
return ((width * height * 3 / 2) / 2) + 128;
/* encoder */
size = FFALIGN(height, 32) * FFALIGN(width, 32) * 3 / 2 / 2;
return FFALIGN(size, SZ_4K);
}
static inline void v4l2_save_to_context(V4L2Context* ctx, struct v4l2_format_update *fmt)
{
ctx->format.type = ctx->type;
if (fmt->update_avfmt)
ctx->av_pix_fmt = fmt->av_fmt;
if (V4L2_TYPE_IS_MULTIPLANAR(ctx->type)) {
/* update the sizes to handle the reconfiguration of the capture stream at runtime */
ctx->format.fmt.pix_mp.height = ctx->height;
ctx->format.fmt.pix_mp.width = ctx->width;
if (fmt->update_v4l2) {
ctx->format.fmt.pix_mp.pixelformat = fmt->v4l2_fmt;
/* s5p-mfc requires the user to specify a buffer size */
ctx->format.fmt.pix_mp.plane_fmt[0].sizeimage =
v4l2_get_framesize_compressed(ctx, ctx->width, ctx->height);
}
} else {
ctx->format.fmt.pix.height = ctx->height;
ctx->format.fmt.pix.width = ctx->width;
if (fmt->update_v4l2) {
ctx->format.fmt.pix.pixelformat = fmt->v4l2_fmt;
/* s5p-mfc requires the user to specify a buffer size */
ctx->format.fmt.pix.sizeimage =
v4l2_get_framesize_compressed(ctx, ctx->width, ctx->height);
}
}
}
static int get_default_selection(V4L2Context * const ctx, struct v4l2_rect *r)
{
V4L2m2mContext * const s = ctx_to_m2mctx(ctx);
struct v4l2_selection selection = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.target = V4L2_SEL_TGT_COMPOSE
};
memset(r, 0, sizeof(*r));
if (ioctl(s->fd, VIDIOC_G_SELECTION, &selection))
return AVERROR(errno);
*r = selection.r;
return 0;
}
static int do_source_change(V4L2m2mContext * const s)
{
AVCodecContext *const avctx = s->avctx;
int ret;
int reinit;
struct v4l2_format cap_fmt = s->capture.format;
s->capture.done = 0;
ret = ioctl(s->fd, VIDIOC_G_FMT, &cap_fmt);
if (ret) {
av_log(avctx, AV_LOG_ERROR, "%s VIDIOC_G_FMT failed\n", s->capture.name);
return 0;
}
get_default_selection(&s->capture, &s->capture.selection);
reinit = ctx_resolution_changed(&s->capture, &cap_fmt);
if ((s->quirks & FF_V4L2_QUIRK_REINIT_ALWAYS) != 0)
reinit = 1;
s->capture.format = cap_fmt;
if (reinit) {
s->capture.height = ff_v4l2_get_format_height(&cap_fmt);
s->capture.width = ff_v4l2_get_format_width(&cap_fmt);
}
// If we don't support selection (or it is bust) and we obviously have HD then kludge
if ((s->capture.selection.width == 0 || s->capture.selection.height == 0) &&
(s->capture.height == 1088 && s->capture.width == 1920)) {
s->capture.selection = (struct v4l2_rect){.width = 1920, .height = 1080};
}
s->capture.sample_aspect_ratio = v4l2_get_sar(&s->capture);
av_log(avctx, AV_LOG_DEBUG, "Source change: Fmt: %s, SAR: %d/%d, wxh %dx%d crop %dx%d @ %d,%d, reinit=%d\n",
av_fourcc2str(ff_v4l2_get_format_pixelformat(&cap_fmt)),
s->capture.sample_aspect_ratio.num, s->capture.sample_aspect_ratio.den,
s->capture.width, s->capture.height,
s->capture.selection.width, s->capture.selection.height,
s->capture.selection.left, s->capture.selection.top, reinit);
ret = ff_v4l2_context_set_status(&s->capture, VIDIOC_STREAMOFF);
if (ret)
av_log(avctx, AV_LOG_ERROR, "capture VIDIOC_STREAMOFF failed\n");
s->draining = 0;
if (!reinit) {
/* Buffers are OK so just stream off to ack */
av_log(avctx, AV_LOG_DEBUG, "%s: Parameters only - restart decode\n", __func__);
}
else {
if (avctx)
ret = ff_set_dimensions(s->avctx,
s->capture.selection.width != 0 ? s->capture.selection.width : s->capture.width,
s->capture.selection.height != 0 ? s->capture.selection.height : s->capture.height);
if (ret < 0)
av_log(avctx, AV_LOG_WARNING, "update avcodec height and width failed\n");
ff_v4l2_context_release(&s->capture);
if (s->capture.width > ff_v4l2_get_format_width(&s->capture.format) ||
s->capture.height > ff_v4l2_get_format_height(&s->capture.format)) {
av_log(avctx, AV_LOG_ERROR, "Format post reinit too small: wanted %dx%d > got %dx%d\n",
s->capture.width, s->capture.height,
ff_v4l2_get_format_width(&s->capture.format), ff_v4l2_get_format_height(&s->capture.format));
return AVERROR(EINVAL);
}
// Update pixel format - should only actually do something on initial change
s->capture.av_pix_fmt =
ff_v4l2_format_v4l2_to_avfmt(ff_v4l2_get_format_pixelformat(&s->capture.format), AV_CODEC_ID_RAWVIDEO);
avctx->pix_fmt = s->output_drm ? AV_PIX_FMT_DRM_PRIME : s->capture.av_pix_fmt;
avctx->sw_pix_fmt = s->capture.av_pix_fmt;
}
ret = ff_v4l2_context_set_status(&s->capture, VIDIOC_STREAMON);
return 1;
}
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);
else
return AVERROR(errno);
}
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);
else
return AVERROR(errno);
}
return 0;
}
// DQ a buffer
// Amalgamates all the various ways there are of signalling EOS/Event to
// generate a consistant EPIPE.
//
// Sets ctx->flag_last if next dq would produce EPIPE (i.e. stream has stopped)
//
// Returns:
// 0 Success
// AVERROR(EPIPE) Nothing more to read
// AVERROR(ENOSPC) No buffers in Q to put result in
// * AVERROR(..)
static int
dq_buf(V4L2Context * const ctx, V4L2Buffer ** const ppavbuf)
{
V4L2m2mContext * const m = ctx_to_m2mctx(ctx);
AVCodecContext * const avctx = m->avctx;
V4L2Buffer * avbuf;
const int is_mp = V4L2_TYPE_IS_MULTIPLANAR(ctx->type);
struct v4l2_plane planes[VIDEO_MAX_PLANES] = {{0}};
struct v4l2_buffer buf = {
.type = ctx->type,
.memory = V4L2_MEMORY_MMAP,
};
*ppavbuf = NULL;
if (ctx->flag_last)
return AVERROR(EPIPE);
if (is_mp) {
buf.length = VIDEO_MAX_PLANES;
buf.m.planes = planes;
}
while (ioctl(m->fd, VIDIOC_DQBUF, &buf) != 0) {
const int err = errno;
av_assert0(AVERROR(err) < 0);
if (err != EINTR) {
av_log(avctx, AV_LOG_DEBUG, "%s VIDIOC_DQBUF, errno (%s)\n",
ctx->name, av_err2str(AVERROR(err)));
if (err == EPIPE)
ctx->flag_last = 1;
return AVERROR(err);
}
}
atomic_fetch_sub(&ctx->q_count, 1);
avbuf = (V4L2Buffer *)ctx->bufrefs[buf.index]->data;
ff_v4l2_buffer_set_avail(avbuf);
avbuf->buf = buf;
if (is_mp) {
memcpy(avbuf->planes, planes, sizeof(planes));
avbuf->buf.m.planes = avbuf->planes;
}
// Done with any attached buffer
av_buffer_unref(&avbuf->ref_buf);
if (V4L2_TYPE_IS_CAPTURE(ctx->type)) {
// Zero length cap buffer return == EOS
if ((is_mp ? buf.m.planes[0].bytesused : buf.bytesused) == 0) {
av_log(avctx, AV_LOG_DEBUG, "Buffer empty - reQ\n");
// Must reQ so we don't leak
// May not matter if the next thing we do is release all the
// buffers but better to be tidy.
ff_v4l2_buffer_enqueue(avbuf);
ctx->flag_last = 1;
return AVERROR(EPIPE);
}
#ifdef V4L2_BUF_FLAG_LAST
// If flag_last set then this contains data but is the last frame
// so remember that but return OK
if ((buf.flags & V4L2_BUF_FLAG_LAST) != 0)
ctx->flag_last = 1;
#endif
}
*ppavbuf = avbuf;
return 0;
}
/**
* handle resolution change event and end of stream event
* Expects to be called after the stream has stopped
*
* returns 1 if reinit was successful, negative if it failed
* returns 0 if reinit was not executed
*/
static int
get_event(V4L2m2mContext * const m)
{
AVCodecContext * const avctx = m->avctx;
struct v4l2_event evt = { 0 };
while (ioctl(m->fd, VIDIOC_DQEVENT, &evt) != 0) {
const int rv = AVERROR(errno);
if (rv == AVERROR(EINTR))
continue;
if (rv == AVERROR(EAGAIN)) {
av_log(avctx, AV_LOG_WARNING, "V4L2 failed to get expected event - assume EOS\n");
return AVERROR_EOF;
}
av_log(avctx, AV_LOG_ERROR, "V4L2 VIDIOC_DQEVENT: %s\n", av_err2str(rv));
return rv;
}
av_log(avctx, AV_LOG_DEBUG, "Dq event %d\n", evt.type);
if (evt.type == V4L2_EVENT_EOS) {
av_log(avctx, AV_LOG_TRACE, "V4L2 VIDIOC_EVENT_EOS\n");
return AVERROR_EOF;
}
if (evt.type == V4L2_EVENT_SOURCE_CHANGE)
return do_source_change(m);
return 0;
}
static inline int
dq_ok(const V4L2Context * const c)
{
return c->streamon && atomic_load(&c->q_count) != 0;
}
// Get a buffer
// If output then just gets the buffer in the expected way
// If capture then runs the capture state m/c to deal with res change etc.
// If return value == 0 then *ppavbuf != NULL
static int
get_qbuf(V4L2Context * const ctx, V4L2Buffer ** const ppavbuf, const int timeout)
{
V4L2m2mContext * const m = ctx_to_m2mctx(ctx);
AVCodecContext * const avctx = m->avctx;
const int is_cap = V4L2_TYPE_IS_CAPTURE(ctx->type);
const unsigned int poll_cap = (POLLIN | POLLRDNORM);
const unsigned int poll_out = (POLLOUT | POLLWRNORM);
const unsigned int poll_event = POLLPRI;
*ppavbuf = NULL;
for (;;) {
struct pollfd pfd = {
.fd = m->fd,
// If capture && stream not started then assume we are waiting for the initial event
.events = !is_cap ? poll_out :
!ff_v4l2_ctx_eos(ctx) && ctx->streamon ? poll_cap :
poll_event,
};
int ret;
if (ctx->done) {
av_log(avctx, AV_LOG_TRACE, "V4L2 %s already done\n", ctx->name);
return AVERROR_EOF;
}
// If capture && timeout == -1 then also wait for rx buffer free
if (is_cap && timeout == -1 && dq_ok(&m->output) && !m->draining)
pfd.events |= poll_out;
// If nothing Qed all we will get is POLLERR - avoid that
if ((pfd.events == poll_out && !dq_ok(&m->output)) ||
(pfd.events == poll_cap && !dq_ok(&m->capture)) ||
(pfd.events == (poll_cap | poll_out) && !dq_ok(&m->capture) && !dq_ok(&m->output))) {
av_log(avctx, AV_LOG_TRACE, "V4L2 poll %s empty\n", ctx->name);
return AVERROR(ENOSPC);
}
// Timeout kludged s.t. "forever" eventually gives up & produces logging
// If waiting for an event when we have seen a last_frame then we expect
// it to be ready already so force a short timeout
ret = poll(&pfd, 1,
ff_v4l2_ctx_eos(ctx) ? 10 :
timeout == -1 ? 3000 : timeout);
if (ret < 0) {
ret = AVERROR(errno); // Remember errno before logging etc.
av_assert0(ret < 0);
}
av_log(avctx, AV_LOG_TRACE, "V4L2 poll %s ret=%d, timeout=%d, events=%#x, revents=%#x\n",
ctx->name, ret, timeout, pfd.events, pfd.revents);
if (ret < 0) {
if (ret == AVERROR(EINTR))
continue;
av_log(avctx, AV_LOG_ERROR, "V4L2 %s poll error %d (%s)\n", ctx->name, AVUNERROR(ret), av_err2str(ret));
return ret;
}
if (ret == 0) {
if (timeout == -1)
av_log(avctx, AV_LOG_ERROR, "V4L2 %s poll unexpected timeout: events=%#x\n", ctx->name, pfd.events);
if (ff_v4l2_ctx_eos(ctx)) {
av_log(avctx, AV_LOG_WARNING, "V4L2 %s poll event timeout\n", ctx->name);
ret = get_event(m);
if (ret < 0) {
ctx->done = 1;
return ret;
}
}
return AVERROR(EAGAIN);
}
if ((pfd.revents & POLLERR) != 0) {
av_log(avctx, AV_LOG_WARNING, "V4L2 %s POLLERR\n", ctx->name);
return AVERROR_UNKNOWN;
}
if ((pfd.revents & poll_event) != 0) {
ret = get_event(m);
if (ret < 0) {
ctx->done = 1;
return ret;
}
continue;
}
if ((pfd.revents & poll_cap) != 0) {
ret = dq_buf(ctx, ppavbuf);
if (ret == AVERROR(EPIPE))
continue;
return ret;
}
if ((pfd.revents & poll_out) != 0) {
if (is_cap)
return AVERROR(EAGAIN);
return dq_buf(ctx, ppavbuf);
}
av_log(avctx, AV_LOG_ERROR, "V4L2 poll unexpected events=%#x, revents=%#x\n", pfd.events, pfd.revents);
return AVERROR_UNKNOWN;
}
}
// Clear out flags and timestamps that should should be set by the user
// Returns the passed avbuf
static V4L2Buffer *
clean_v4l2_buffer(V4L2Buffer * const avbuf)
{
struct v4l2_buffer *const buf = &avbuf->buf;
buf->flags = 0;
buf->field = V4L2_FIELD_ANY;
buf->timestamp = (struct timeval){0};
buf->timecode = (struct v4l2_timecode){0};
buf->sequence = 0;
return avbuf;
}
int
ff_v4l2_dq_all(V4L2Context *const ctx, int timeout1)
{
V4L2Buffer * avbuf;
if (timeout1 != 0) {
int rv = get_qbuf(ctx, &avbuf, timeout1);
if (rv != 0)
return rv;
}
do {
get_qbuf(ctx, &avbuf, 0);
} while (avbuf);
return 0;
}
static V4L2Buffer* v4l2_getfree_v4l2buf(V4L2Context *ctx)
{
int i;
/* get back as many output buffers as possible */
if (V4L2_TYPE_IS_OUTPUT(ctx->type))
ff_v4l2_dq_all(ctx, 0);
for (i = 0; i < ctx->num_buffers; i++) {
V4L2Buffer * const avbuf = (V4L2Buffer *)ctx->bufrefs[i]->data;
if (avbuf->status == V4L2BUF_AVAILABLE)
return clean_v4l2_buffer(avbuf);
}
return NULL;
}
static int v4l2_release_buffers(V4L2Context* ctx)
{
int i;
int ret = 0;
const int fd = ctx_to_m2mctx(ctx)->fd;
// Orphan any buffers in the wild
ff_weak_link_break(&ctx->wl_master);
if (ctx->bufrefs) {
for (i = 0; i < ctx->num_buffers; i++)
av_buffer_unref(ctx->bufrefs + i);
}
if (fd != -1) {
struct v4l2_requestbuffers req = {
.memory = V4L2_MEMORY_MMAP,
.type = ctx->type,
.count = 0, /* 0 -> unmap all buffers from the driver */
};
while ((ret = ioctl(fd, VIDIOC_REQBUFS, &req)) == -1) {
if (errno == EINTR)
continue;
ret = AVERROR(errno);
av_log(logger(ctx), AV_LOG_ERROR, "release all %s buffers (%s)\n",
ctx->name, av_err2str(AVERROR(errno)));
if (ctx_to_m2mctx(ctx)->output_drm)
av_log(logger(ctx), AV_LOG_ERROR,
"Make sure the DRM client releases all FB/GEM objects before closing the codec (ie):\n"
"for all buffers: \n"
" 1. drmModeRmFB(..)\n"
" 2. drmIoctl(.., DRM_IOCTL_GEM_CLOSE,... )\n");
}
}
atomic_store(&ctx->q_count, 0);
return ret;
}
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)
{
V4L2m2mContext* s = ctx_to_m2mctx(ctx);
V4L2m2mPriv *priv = s->avctx->priv_data;
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 (;; ++fdesc.index) {
ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_ENUM_FMT, &fdesc);
if (ret)
return AVERROR(EINVAL);
if (priv->pix_fmt != AV_PIX_FMT_NONE) {
if (fdesc.pixelformat != ff_v4l2_format_avfmt_to_v4l2(priv->pix_fmt))
continue;
}
pixfmt = ff_v4l2_format_v4l2_to_avfmt(fdesc.pixelformat, AV_CODEC_ID_RAWVIDEO);
ret = v4l2_try_raw_format(ctx, pixfmt);
if (ret == 0) {
*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
*
*****************************************************************************/
static void flush_all_buffers_status(V4L2Context* const ctx)
{
int i;
if (!ctx->bufrefs)
return;
for (i = 0; i < ctx->num_buffers; ++i) {
struct V4L2Buffer * const buf = (struct V4L2Buffer *)ctx->bufrefs[i]->data;
if (buf->status == V4L2BUF_IN_DRIVER)
ff_v4l2_buffer_set_avail(buf);
}
atomic_store(&ctx->q_count, 0);
}
static int stuff_all_buffers(AVCodecContext * avctx, V4L2Context* ctx)
{
int i;
int rv;
if (!ctx->bufrefs) {
rv = ff_v4l2_context_init(ctx);
if (rv) {
av_log(avctx, AV_LOG_ERROR, "can't request capture buffers\n");
return rv;
}
}
ff_mutex_lock(&ctx->lock);
for (i = 0; i < ctx->num_buffers; ++i) {
struct V4L2Buffer * const buf = (struct V4L2Buffer *)ctx->bufrefs[i]->data;
if (buf->status == V4L2BUF_AVAILABLE) {
rv = ff_v4l2_buffer_enqueue(buf);
if (rv < 0)
break;
}
}
ff_mutex_unlock(&ctx->lock);
return rv;
}
static int set_streamon(AVCodecContext * const avctx, V4L2Context*const ctx)
{
int type = ctx->type;
int ret = 0;
if (!V4L2_TYPE_IS_OUTPUT(ctx->type))
stuff_all_buffers(avctx, ctx);
if (ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_STREAMON, &type) < 0) {
ret = AVERROR(errno);
av_log(avctx, AV_LOG_ERROR, "%s set status ON failed: err=%s\n", ctx->name,
av_err2str(ret));
return ret;
}
ctx->first_buf = 1;
ctx->streamon = 1;
ctx->flag_last = 0;
av_log(avctx, AV_LOG_DEBUG, "%s set status ON OK\n", ctx->name);
return ret;
}
static int set_streamoff(AVCodecContext * const avctx, V4L2Context*const ctx)
{
int type = ctx->type;
int ret = 0;
const int has_bufs = ctx_buffers_alloced(ctx);
// Avoid doing anything if there is nothing we can do
if (!has_bufs && !ctx->streamon)
return 0;
if (has_bufs)
ff_mutex_lock(&ctx->lock);
if (ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_STREAMOFF, &type) < 0) {
ret = AVERROR(errno);
av_log(avctx, AV_LOG_ERROR, "%s set status ON failed: err=%s\n", ctx->name,
av_err2str(ret));
}
else {
flush_all_buffers_status(ctx);
ctx->streamon = 0;
ctx->flag_last = 0;
av_log(avctx, AV_LOG_DEBUG, "%s set status OFF OK\n", ctx->name);
}
if (has_bufs)
ff_mutex_unlock(&ctx->lock);
return ret;
}
int ff_v4l2_context_set_status(V4L2Context* ctx, uint32_t cmd)
{
AVCodecContext * const avctx = logger(ctx);
switch (cmd) {
case VIDIOC_STREAMOFF:
return set_streamoff(avctx, ctx);
case VIDIOC_STREAMON:
return set_streamon(avctx, ctx);
default:
av_log(avctx, AV_LOG_ERROR, "%s: Unexpected cmd: %d\n", __func__, cmd);
break;
}
return AVERROR_BUG;
}
int ff_v4l2_context_enqueue_frame(V4L2Context* ctx, const AVFrame* frame)
{
V4L2m2mContext *const s = ctx_to_m2mctx(ctx);
AVCodecContext *const avctx = s->avctx;
int64_t track_ts;
V4L2Buffer* avbuf;
int ret;
if (!frame) {
ret = v4l2_stop_encode(ctx);
if (ret)
av_log(avctx, AV_LOG_ERROR, "%s stop_encode\n", ctx->name);
s->draining= 1;
return 0;
}
avbuf = v4l2_getfree_v4l2buf(ctx);
if (!avbuf)
return AVERROR(EAGAIN);
track_ts = xlat_pts_frame_in(avctx, &s->xlat, frame);
ret = ff_v4l2_buffer_avframe_to_buf(frame, avbuf, track_ts);
if (ret)
return ret;
return ff_v4l2_buffer_enqueue(avbuf);
}
int ff_v4l2_context_enqueue_packet(V4L2Context* ctx, const AVPacket* pkt,
const void * extdata, size_t extlen)
{
V4L2m2mContext *s = ctx_to_m2mctx(ctx);
AVCodecContext *const avctx = s->avctx;
V4L2Buffer* avbuf;
int ret;
int64_t track_ts;
if (!pkt->size) {
ret = v4l2_stop_decode(ctx);
// Log but otherwise ignore stop failure
if (ret)
av_log(avctx, AV_LOG_ERROR, "%s stop_decode failed: err=%d\n", ctx->name, ret);
s->draining = 1;
return 0;
}
avbuf = v4l2_getfree_v4l2buf(ctx);
if (!avbuf)
return AVERROR(EAGAIN);
track_ts = xlat_pts_pkt_in(avctx, &s->xlat, pkt);
ret = ff_v4l2_buffer_avpkt_to_buf_ext(pkt, avbuf, extdata, extlen, track_ts);
if (ret == AVERROR(ENOMEM))
av_log(logger(ctx), AV_LOG_ERROR, "Buffer overflow in %s: pkt->size=%d > buf->length=%d\n",
__func__, pkt->size, avbuf->planes[0].length);
else if (ret)
return ret;
return ff_v4l2_buffer_enqueue(avbuf);
}
int ff_v4l2_context_dequeue_frame(V4L2Context* ctx, AVFrame* frame, int timeout)
{
V4L2m2mContext *s = ctx_to_m2mctx(ctx);
AVCodecContext *const avctx = s->avctx;
V4L2Buffer *avbuf;
int rv;
do {
if ((rv = get_qbuf(ctx, &avbuf, timeout)) != 0)
return rv;
if ((rv = ff_v4l2_buffer_buf_to_avframe(frame, avbuf)) != 0)
return rv;
} while (xlat_pts_frame_out(avctx, &s->xlat, frame) != 0);
return 0;
}
int ff_v4l2_context_dequeue_packet(V4L2Context* ctx, AVPacket* pkt, int timeout)
{
V4L2m2mContext *s = ctx_to_m2mctx(ctx);
AVCodecContext *const avctx = s->avctx;
V4L2Buffer *avbuf;
int rv;
do {
if ((rv = get_qbuf(ctx, &avbuf, timeout)) != 0)
return rv == AVERROR(ENOSPC) ? AVERROR(EAGAIN) : rv; // Caller not currently expecting ENOSPC
if ((rv = ff_v4l2_buffer_buf_to_avpkt(pkt, avbuf)) != 0)
return rv;
} while (xlat_pts_pkt_out(avctx, &s->xlat, pkt) != 0);
return 0;
}
// Return 0 terminated list of drm fourcc video formats for this context
// NULL if none found or error
// Returned list is malloced so must be freed
uint32_t * ff_v4l2_context_enum_drm_formats(V4L2Context *ctx, unsigned int *pN)
{
unsigned int i;
unsigned int n = 0;
unsigned int size = 0;
uint32_t * e = NULL;
*pN = 0;
for (i = 0; i < 1024; ++i) {
struct v4l2_fmtdesc fdesc = {
.index = i,
.type = ctx->type
};
if (ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_ENUM_FMT, &fdesc))
return e;
if (n + 1 >= size) {
unsigned int newsize = (size == 0) ? 16 : size * 2;
uint32_t * t = av_realloc(e, newsize * sizeof(*t));
if (!t)
return e;
e = t;
size = newsize;
}
e[n] = fdesc.pixelformat;
e[++n] = 0;
if (pN)
*pN = n;
}
// If we've looped 1024 times we are clearly confused
*pN = 0;
av_free(e);
return NULL;
}
int ff_v4l2_context_get_format(V4L2Context* ctx, int probe)
{
struct v4l2_format_update fmt = { 0 };
int ret;
if (ctx->av_codec_id == AV_CODEC_ID_RAWVIDEO) {
ret = v4l2_get_raw_format(ctx, &fmt.av_fmt);
if (ret)
return ret;
fmt.update_avfmt = !probe;
v4l2_save_to_context(ctx, &fmt);
/* format has been tried already */
return ret;
}
ret = v4l2_get_coded_format(ctx, &fmt.v4l2_fmt);
if (ret)
return ret;
fmt.update_v4l2 = 1;
v4l2_save_to_context(ctx, &fmt);
return ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_TRY_FMT, &ctx->format);
}
int ff_v4l2_context_set_format(V4L2Context* ctx)
{
int ret;
ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_S_FMT, &ctx->format);
if (ret != 0)
return ret;
// Check returned size against min size and if smaller have another go
// Only worry about plane[0] as this is meant to enforce limits for
// encoded streams where we might know a bit more about the shape
// than the driver
if (V4L2_TYPE_IS_MULTIPLANAR(ctx->format.type)) {
if (ctx->min_buf_size <= ctx->format.fmt.pix_mp.plane_fmt[0].sizeimage)
return 0;
ctx->format.fmt.pix_mp.plane_fmt[0].sizeimage = ctx->min_buf_size;
}
else {
if (ctx->min_buf_size <= ctx->format.fmt.pix.sizeimage)
return 0;
ctx->format.fmt.pix.sizeimage = ctx->min_buf_size;
}
ret = ioctl(ctx_to_m2mctx(ctx)->fd, VIDIOC_S_FMT, &ctx->format);
return ret;
}
void ff_v4l2_context_release(V4L2Context* ctx)
{
int ret;
if (!ctx->bufrefs)
return;
ret = v4l2_release_buffers(ctx);
if (ret)
av_log(logger(ctx), AV_LOG_WARNING, "V4L2 failed to unmap the %s buffers\n", ctx->name);
av_freep(&ctx->bufrefs);
av_buffer_unref(&ctx->frames_ref);
ff_mutex_destroy(&ctx->lock);
pthread_cond_destroy(&ctx->cond);
}
static int create_buffers(V4L2Context* const ctx, const unsigned int req_buffers, const enum v4l2_memory mem)
{
V4L2m2mContext * const s = ctx_to_m2mctx(ctx);
struct v4l2_requestbuffers req;
int ret;
int i;
av_assert0(ctx->bufrefs == NULL);
memset(&req, 0, sizeof(req));
req.count = req_buffers;
req.memory = mem;
req.type = ctx->type;
while ((ret = ioctl(s->fd, VIDIOC_REQBUFS, &req)) == -1) {
if (errno != EINTR) {
ret = AVERROR(errno);
av_log(logger(ctx), AV_LOG_ERROR, "%s VIDIOC_REQBUFS failed: %s\n", ctx->name, av_err2str(ret));
return ret;
}
}
ctx->num_buffers = req.count;
ctx->bufrefs = av_mallocz(ctx->num_buffers * sizeof(*ctx->bufrefs));
if (!ctx->bufrefs) {
av_log(logger(ctx), AV_LOG_ERROR, "%s malloc enomem\n", ctx->name);
goto fail_release;
}
ctx->wl_master = ff_weak_link_new(ctx);
if (!ctx->wl_master) {
ret = AVERROR(ENOMEM);
goto fail_release;
}
for (i = 0; i < ctx->num_buffers; i++) {
ret = ff_v4l2_buffer_initialize(&ctx->bufrefs[i], i, ctx, mem);
if (ret) {
av_log(logger(ctx), AV_LOG_ERROR, "%s buffer[%d] initialization (%s)\n", ctx->name, i, av_err2str(ret));
goto fail_release;
}
}
av_log(logger(ctx), AV_LOG_DEBUG, "%s: %s %02d buffers initialized: %04ux%04u, sizeimage %08u, bytesperline %08u\n", ctx->name,
V4L2_TYPE_IS_MULTIPLANAR(ctx->type) ? av_fourcc2str(ctx->format.fmt.pix_mp.pixelformat) : av_fourcc2str(ctx->format.fmt.pix.pixelformat),
req.count,
ff_v4l2_get_format_width(&ctx->format),
ff_v4l2_get_format_height(&ctx->format),
V4L2_TYPE_IS_MULTIPLANAR(ctx->type) ? ctx->format.fmt.pix_mp.plane_fmt[0].sizeimage : ctx->format.fmt.pix.sizeimage,
V4L2_TYPE_IS_MULTIPLANAR(ctx->type) ? ctx->format.fmt.pix_mp.plane_fmt[0].bytesperline : ctx->format.fmt.pix.bytesperline);
return 0;
fail_release:
v4l2_release_buffers(ctx);
av_freep(&ctx->bufrefs);
return ret;
}
int ff_v4l2_context_frames_set(V4L2Context *const ctx)
{
AVHWFramesContext *hwframes;
V4L2m2mContext * const s = ctx_to_m2mctx(ctx);
const int w = ctx->width != 0 ? ctx->width : s->avctx->width;
const int h = ctx->height != 0 ? ctx->height : s->avctx->height;
int ret;
if (ctx->frames_ref != NULL) {
const AVHWFramesContext * const hwf = (AVHWFramesContext*)ctx->frames_ref->data;
if (hwf->sw_format == ctx->av_pix_fmt && hwf->width == w && hwf->height == h)
return 0;
av_buffer_unref(&ctx->frames_ref);
}
ctx->frames_ref = av_hwframe_ctx_alloc(s->device_ref);
if (!ctx->frames_ref)
return AVERROR(ENOMEM);
hwframes = (AVHWFramesContext*)ctx->frames_ref->data;
hwframes->format = AV_PIX_FMT_DRM_PRIME;
hwframes->sw_format = ctx->av_pix_fmt;
hwframes->width = w;
hwframes->height = h;
ret = av_hwframe_ctx_init(ctx->frames_ref);
if (ret < 0) {
av_log(s->avctx, AV_LOG_ERROR, "Failed to create hwframes context: %s\n", av_err2str(ret));
av_buffer_unref(&ctx->frames_ref);
return ret;
}
av_log(s->avctx, AV_LOG_DEBUG, "%s: HWFramesContext set to %s, %dx%d\n", __func__,
av_get_pix_fmt_name(ctx->av_pix_fmt), w, h);
return 0;
}
int ff_v4l2_context_init(V4L2Context* ctx)
{
struct v4l2_queryctrl qctrl;
V4L2m2mContext * const s = ctx_to_m2mctx(ctx);
int ret;
// It is not valid to reinit a context without a previous release
av_assert0(ctx->bufrefs == NULL);
if (!v4l2_type_supported(ctx)) {
av_log(logger(ctx), AV_LOG_ERROR, "type %i not supported\n", ctx->type);
return AVERROR_PATCHWELCOME;
}
ff_mutex_init(&ctx->lock, NULL);
pthread_cond_init(&ctx->cond, NULL);
atomic_init(&ctx->q_count, 0);
ret = ioctl(s->fd, VIDIOC_G_FMT, &ctx->format);
if (ret) {
ret = AVERROR(errno);
av_log(logger(ctx), AV_LOG_ERROR, "%s VIDIOC_G_FMT failed: %s\n", ctx->name, av_err2str(ret));
goto fail_unlock;
}
memset(&qctrl, 0, sizeof(qctrl));
qctrl.id = V4L2_CID_MIN_BUFFERS_FOR_OUTPUT;
if (ioctl(s->fd, VIDIOC_QUERYCTRL, &qctrl) != 0) {
ret = AVERROR(errno);
if (ret != AVERROR(EINVAL)) {
av_log(logger(ctx), AV_LOG_ERROR, "%s VIDIOC_QUERCTRL failed: %s\n", ctx->name, av_err2str(ret));
goto fail_unlock;
}
// Control unsupported - set default if wanted
if (ctx->num_buffers < 2)
ctx->num_buffers = 4;
}
else {
if (ctx->num_buffers < 2)
ctx->num_buffers = qctrl.minimum + 2;
ctx->num_buffers = av_clip(ctx->num_buffers, qctrl.minimum, qctrl.maximum);
}
ret = create_buffers(ctx, ctx->num_buffers, ctx->buf_mem);
if (ret < 0)
goto fail_unlock;
return 0;
fail_unlock:
ff_mutex_destroy(&ctx->lock);
return ret;
}