1356 lines
40 KiB
C
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;
|
|
}
|