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

1393 lines
46 KiB
C

/*
* V4L2 mem2mem decoders
*
* 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 "config_components.h"
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include "libavutil/avassert.h"
#include "libavutil/hwcontext.h"
#include "libavutil/hwcontext_drm.h"
#include "libavutil/mem.h"
#include "libavutil/pixfmt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "codec_internal.h"
#include "libavcodec/decode.h"
#include "libavcodec/hwaccels.h"
#include "libavcodec/internal.h"
#include "libavcodec/hwconfig.h"
#include "v4l2_context.h"
#include "v4l2_m2m.h"
#include "v4l2_fmt.h"
#include "v4l2_req_dmabufs.h"
#if CONFIG_H264_DECODER
#include "h264_parse.h"
#endif
#if CONFIG_HEVC_DECODER
#include "hevc/parse.h"
#endif
// Pick 64 for max last count - that is >1sec at 60fps
#define STATS_LAST_COUNT_MAX 64
#define STATS_INTERVAL_MAX (1 << 30)
#ifndef FF_API_BUFFER_SIZE_T
#define FF_API_BUFFER_SIZE_T 1
#endif
#define DUMP_FAILED_EXTRADATA 0
#if DUMP_FAILED_EXTRADATA
static inline char hex1(unsigned int x)
{
x &= 0xf;
return x <= 9 ? '0' + x : 'a' + x - 10;
}
static inline char * hex2(char * s, unsigned int x)
{
*s++ = hex1(x >> 4);
*s++ = hex1(x);
return s;
}
static inline char * hex4(char * s, unsigned int x)
{
s = hex2(s, x >> 8);
s = hex2(s, x);
return s;
}
static inline char * dash2(char * s)
{
*s++ = '-';
*s++ = '-';
return s;
}
static void
data16(char * s, const unsigned int offset, const uint8_t * m, const size_t len)
{
size_t i;
s = hex4(s, offset);
m += offset;
for (i = 0; i != 8; ++i) {
*s++ = ' ';
s = len > i + offset ? hex2(s, *m++) : dash2(s);
}
*s++ = ' ';
*s++ = ':';
for (; i != 16; ++i) {
*s++ = ' ';
s = len > i + offset ? hex2(s, *m++) : dash2(s);
}
*s++ = 0;
}
static void
log_dump(void * logctx, int lvl, const void * const data, const size_t len)
{
size_t i;
for (i = 0; i < len; i += 16) {
char buf[80];
data16(buf, i, data, len);
av_log(logctx, lvl, "%s\n", buf);
}
}
#endif
static unsigned int pts_stats_interval(const pts_stats_t * const stats)
{
return stats->last_interval;
}
static int64_t pts_stats_guess(const pts_stats_t * const stats, const int fail_bad_guess)
{
if (stats->last_count <= 1)
return stats->last_pts;
if (stats->last_pts == AV_NOPTS_VALUE ||
fail_bad_guess && (stats->last_interval == 0 ||
stats->last_count >= STATS_LAST_COUNT_MAX))
return AV_NOPTS_VALUE;
return stats->last_pts + (int64_t)(stats->last_count - 1) * (int64_t)stats->last_interval;
}
static void pts_stats_add(pts_stats_t * const stats, int64_t pts)
{
if (pts == AV_NOPTS_VALUE || pts == stats->last_pts) {
if (stats->last_count < STATS_LAST_COUNT_MAX)
++stats->last_count;
return;
}
if (stats->last_pts != AV_NOPTS_VALUE) {
const int64_t interval = pts - stats->last_pts;
if (interval < 0 || interval >= STATS_INTERVAL_MAX ||
stats->last_count >= STATS_LAST_COUNT_MAX) {
if (stats->last_interval != 0)
av_log(stats->logctx, AV_LOG_DEBUG, "%s: %s: Bad interval: %" PRId64 "/%d\n",
__func__, stats->name, interval, stats->last_count);
stats->last_interval = 0;
}
else {
const int64_t frame_time = interval / (int64_t)stats->last_count;
if (frame_time != stats->last_interval)
av_log(stats->logctx, AV_LOG_DEBUG, "%s: %s: New interval: %u->%" PRId64 "/%d=%" PRId64 "\n",
__func__, stats->name, stats->last_interval, interval, stats->last_count, frame_time);
stats->last_interval = frame_time;
}
}
stats->last_pts = pts;
stats->last_count = 1;
}
static void pts_stats_init(pts_stats_t * const stats, void * logctx, const char * name)
{
*stats = (pts_stats_t){
.logctx = logctx,
.name = name,
.last_count = 1,
.last_interval = 0,
.last_pts = AV_NOPTS_VALUE
};
}
// If abdata == NULL then this just counts space required
// Unpacks avcC if detected
static int
h264_xd_copy(const uint8_t * const extradata, const int extrasize, uint8_t * abdata)
{
const uint8_t * const xdend = extradata + extrasize;
const uint8_t * p = extradata;
uint8_t * d = abdata;
unsigned int n;
unsigned int len;
const unsigned int hdrlen = 4;
unsigned int need_pps = 1;
if (extrasize < 8)
return AVERROR(EINVAL);
if (p[0] == 0 && p[1] == 0) {
// Assume a couple of leading zeros are good enough to indicate NAL
if (abdata)
memcpy(d, p, extrasize);
return extrasize;
}
// avcC starts with a 1
if (p[0] != 1)
return AVERROR(EINVAL);
p += 5;
n = *p++ & 0x1f;
doxps:
while (n--) {
if (xdend - p < 2)
return AVERROR(EINVAL);
len = (p[0] << 8) | p[1];
p += 2;
if (xdend - p < (ptrdiff_t)len)
return AVERROR(EINVAL);
if (abdata) {
d[0] = 0;
d[1] = 0;
d[2] = 0;
d[3] = 1;
memcpy(d + 4, p, len);
}
d += len + hdrlen;
p += len;
}
if (need_pps) {
need_pps = 0;
if (p >= xdend)
return AVERROR(EINVAL);
n = *p++;
goto doxps;
}
return d - abdata;
}
static int
copy_extradata(AVCodecContext * const avctx,
const void * const src_data, const int src_len,
void ** const pdst_data, size_t * const pdst_len)
{
int len;
*pdst_len = 0;
av_freep(pdst_data);
if (avctx->codec_id == AV_CODEC_ID_H264)
len = h264_xd_copy(src_data, src_len, NULL);
else
len = src_len < 0 ? AVERROR(EINVAL) : src_len;
// Zero length is OK but we want to stop - -ve is error val
if (len <= 0)
return len;
if ((*pdst_data = av_malloc(len + AV_INPUT_BUFFER_PADDING_SIZE)) == NULL)
return AVERROR(ENOMEM);
if (avctx->codec_id == AV_CODEC_ID_H264)
h264_xd_copy(src_data, src_len, *pdst_data);
else
memcpy(*pdst_data, src_data, len);
*pdst_len = len;
return 0;
}
static int check_output_streamon(AVCodecContext *const avctx, V4L2m2mContext *const s)
{
int ret;
struct v4l2_decoder_cmd cmd = {
.cmd = V4L2_DEC_CMD_START,
.flags = 0,
};
if (s->output.streamon)
return 0;
ret = ff_v4l2_context_set_status(&s->output, VIDIOC_STREAMON);
if (ret != 0) {
av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMON on output context: %s\n", av_err2str(ret));
return ret;
}
// STREAMON should do implicit START so this just for those that don't.
// It is optional so don't worry if it fails
if (ioctl(s->fd, VIDIOC_DECODER_CMD, &cmd) < 0) {
ret = AVERROR(errno);
av_log(avctx, AV_LOG_WARNING, "VIDIOC_DECODER_CMD start error: %s\n", av_err2str(ret));
}
else {
av_log(avctx, AV_LOG_TRACE, "VIDIOC_DECODER_CMD start OK\n");
}
return 0;
}
static int v4l2_try_start(AVCodecContext *avctx)
{
V4L2m2mContext * const s = ((V4L2m2mPriv*)avctx->priv_data)->context;
int ret;
/* 1. start the output process */
if ((ret = check_output_streamon(avctx, s)) != 0)
return ret;
return 0;
}
static int v4l2_prepare_decoder(V4L2m2mContext *s)
{
struct v4l2_event_subscription sub;
V4L2Context *output = &s->output;
int ret;
/**
* requirements
*/
memset(&sub, 0, sizeof(sub));
sub.type = V4L2_EVENT_SOURCE_CHANGE;
ret = ioctl(s->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
if ( ret < 0) {
if (output->height == 0 || output->width == 0) {
av_log(s->avctx, AV_LOG_ERROR,
"the v4l2 driver does not support VIDIOC_SUBSCRIBE_EVENT\n"
"you must provide codec_height and codec_width on input\n");
return ret;
}
}
memset(&sub, 0, sizeof(sub));
sub.type = V4L2_EVENT_EOS;
ret = ioctl(s->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
if (ret < 0)
av_log(s->avctx, AV_LOG_WARNING,
"the v4l2 driver does not support end of stream VIDIOC_SUBSCRIBE_EVENT\n");
return 0;
}
static void
set_best_effort_pts(AVCodecContext *const avctx,
pts_stats_t * const ps,
AVFrame *const frame)
{
pts_stats_add(ps, frame->pts);
frame->best_effort_timestamp = pts_stats_guess(ps, 1);
// If we can't guess from just PTS - try DTS
if (frame->best_effort_timestamp == AV_NOPTS_VALUE)
frame->best_effort_timestamp = frame->pkt_dts;
// We can't emulate what s/w does in a useful manner and using the
// "correct" answer seems to just confuse things.
frame->pkt_dts = frame->pts;
av_log(avctx, AV_LOG_TRACE, "Out PTS=%" PRId64 "/%"PRId64", DTS=%" PRId64 "\n",
frame->pts, frame->best_effort_timestamp, frame->pkt_dts);
}
static void
xlat_flush(xlat_track_t * const x)
{
unsigned int i;
// Do not reset track_no - this ensures that any frames left in the decoder
// that turn up later get discarded.
x->last_pts = AV_NOPTS_VALUE;
for (i = 0; i != FF_V4L2_M2M_TRACK_SIZE; ++i) {
x->track_els[i].pending = 0;
x->track_els[i].discard = 1;
}
}
static void
xlat_init(xlat_track_t * const x)
{
memset(x, 0, sizeof(*x));
xlat_flush(x);
}
static int
xlat_pending(const V4L2m2mContext * const s)
{
const xlat_track_t *const x = &s->xlat;
unsigned int n = x->track_no % FF_V4L2_M2M_TRACK_SIZE;
int i;
const int64_t now = pts_stats_guess(&s->pts_stat, 0);
int64_t first_dts = AV_NOPTS_VALUE;
int no_dts_count = 0;
unsigned int interval = pts_stats_interval(&s->pts_stat);
for (i = 0; i < FF_V4L2_M2M_TRACK_SIZE; ++i, n = (n - 1) & (FF_V4L2_M2M_TRACK_SIZE - 1)) {
const V4L2m2mTrackEl * const t = x->track_els + n;
if (first_dts == AV_NOPTS_VALUE)
if (t->dts == AV_NOPTS_VALUE)
++no_dts_count;
else
first_dts = t->dts;
// Discard only set on never-set or flushed entries
// So if we get here we've never successfully decoded a frame so allow
// more frames into the buffer before stalling
if (t->discard)
return i - 16;
// If we've got this frame out then everything before this point
// must have entered the decoder
if (!t->pending)
break;
// If we've never seen a pts all we can do is count frames
if (now == AV_NOPTS_VALUE)
continue;
if (t->dts != AV_NOPTS_VALUE && now >= t->dts)
break;
}
if (first_dts != AV_NOPTS_VALUE && now != AV_NOPTS_VALUE && interval != 0 && s->reorder_size != 0) {
const int iframes = (first_dts - now) / (int)interval;
const int t = iframes - s->reorder_size + no_dts_count;
// av_log(s->avctx, AV_LOG_DEBUG, "Last:%"PRId64", Now:%"PRId64", First:%"PRId64", delta=%"PRId64", frames=%d, nodts=%d\n",
// x->last_dts, now, first_dts, first_dts - now, iframes, no_dts_count);
if (iframes > 0 && iframes < 64 && t < i) {
return t;
}
}
return i;
}
static inline int stream_started(const V4L2m2mContext * const s) {
return s->output.streamon;
}
#define NQ_OK 0
#define NQ_Q_FULL 1
#define NQ_SRC_EMPTY 2
#define NQ_NONE 3
#define NQ_DRAINING 4
#define NQ_DEAD 5
#define TRY_DQ(nq_status) ((nq_status) >= NQ_OK && (nq_status) <= NQ_DRAINING)
#define RETRY_NQ(nq_status) ((nq_status) == NQ_Q_FULL || (nq_status) == NQ_NONE)
// do_not_get If true then no new packet will be got but status will
// be set appropriately
// AVERROR_EOF Flushing an already flushed stream
// -ve Error (all errors except EOF are unexpected)
// NQ_OK (0) OK
// NQ_Q_FULL Dst full (retry if we think V4L2 Q has space now)
// NQ_SRC_EMPTY Src empty (do not retry)
// NQ_NONE Enqueue not attempted
// NQ_DRAINING At EOS, dQ dest until EOS there too
// NQ_DEAD Not running (do not retry, do not attempt capture dQ)
static int try_enqueue_src(AVCodecContext * const avctx, V4L2m2mContext * const s, const int do_not_get)
{
int ret;
// If we don't already have a coded packet - get a new one
// We will already have a coded pkt if the output Q was full last time we
// tried to Q it
if (!s->buf_pkt.size && !do_not_get) {
unsigned int i;
for (i = 0; i < 256; ++i) {
uint8_t * side_data;
size_t side_size;
ret = ff_decode_get_packet(avctx, &s->buf_pkt);
if (ret != 0)
break;
// New extradata is the only side-data we undertand
side_data = av_packet_get_side_data(&s->buf_pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size);
if (side_data) {
av_log(avctx, AV_LOG_DEBUG, "New extradata\n");
if ((ret = copy_extradata(avctx, side_data, (int)side_size, &s->extdata_data, &s->extdata_size)) < 0)
av_log(avctx, AV_LOG_WARNING, "Failed to copy new extra data: %s\n", av_err2str(ret));
s->extdata_sent = 0;
}
if (s->buf_pkt.size != 0)
break;
if (s->buf_pkt.side_data_elems == 0) {
av_log(avctx, AV_LOG_WARNING, "Empty pkt from ff_decode_get_packet - treating as EOF\n");
ret = AVERROR_EOF;
break;
}
// Retry a side-data only pkt
}
// If i >= 256 something has gone wrong
if (i >= 256) {
av_log(avctx, AV_LOG_ERROR, "Too many side-data only packets\n");
return AVERROR(EIO);
}
if (ret == AVERROR(EAGAIN)) {
if (!stream_started(s)) {
av_log(avctx, AV_LOG_TRACE, "%s: receive_frame before 1st coded packet\n", __func__);
return NQ_DEAD;
}
return NQ_SRC_EMPTY;
}
if (ret == AVERROR_EOF) {
// EOF - enter drain mode
av_log(avctx, AV_LOG_TRACE, "--- EOS req: ret=%d, size=%d, started=%d, drain=%d\n",
ret, s->buf_pkt.size, stream_started(s), s->draining);
if (!stream_started(s)) {
av_log(avctx, AV_LOG_DEBUG, "EOS on flushed stream\n");
s->draining = 1;
s->capture.done = 1;
return AVERROR_EOF;
}
if (!s->draining) {
// Calling enqueue with an empty pkt starts drain
av_assert0(s->buf_pkt.size == 0);
ret = ff_v4l2_context_enqueue_packet(&s->output, &s->buf_pkt, NULL, 0);
if (ret) {
av_log(avctx, AV_LOG_ERROR, "Failed to start drain: ret=%d\n", ret);
return ret;
}
}
return NQ_DRAINING;
}
if (ret < 0) {
av_log(avctx, AV_LOG_ERROR, "Failed to get coded packet: err=%d\n", ret);
return ret;
}
}
if (s->draining) {
if (s->buf_pkt.size) {
av_log(avctx, AV_LOG_WARNING, "Unexpected input whilst draining\n");
av_packet_unref(&s->buf_pkt);
}
return NQ_DRAINING;
}
if (!s->buf_pkt.size)
return NQ_NONE;
if ((ret = check_output_streamon(avctx, s)) != 0)
return ret;
if (s->extdata_sent)
ret = ff_v4l2_context_enqueue_packet(&s->output, &s->buf_pkt, NULL, 0);
else
ret = ff_v4l2_context_enqueue_packet(&s->output, &s->buf_pkt, s->extdata_data, s->extdata_size);
if (ret == AVERROR(EAGAIN)) {
// Out of input buffers - keep packet
ret = NQ_Q_FULL;
}
else {
// In all other cases we are done with this packet
av_packet_unref(&s->buf_pkt);
s->extdata_sent = 1;
if (ret) {
av_log(avctx, AV_LOG_ERROR, "Packet enqueue failure: err=%d\n", ret);
return ret;
}
}
// Start if we haven't
{
const int ret2 = v4l2_try_start(avctx);
if (ret2) {
av_log(avctx, AV_LOG_DEBUG, "Start failure: err=%d\n", ret2);
ret = (ret2 == AVERROR(ENOMEM)) ? ret2 : NQ_DEAD;
}
}
return ret;
}
static int qbuf_wait(AVCodecContext * const avctx, V4L2Context * const ctx)
{
int rv = 0;
ff_mutex_lock(&ctx->lock);
while (atomic_load(&ctx->q_count) == 0 && ctx->streamon) {
if (pthread_cond_wait(&ctx->cond, &ctx->lock) != 0) {
rv = AVERROR(errno);
av_log(avctx, AV_LOG_ERROR, "Cond wait failure: %s\n", av_err2str(rv));
break;
}
}
ff_mutex_unlock(&ctx->lock);
return rv;
}
static int v4l2_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
V4L2m2mContext *const s = ((V4L2m2mPriv*)avctx->priv_data)->context;
int src_rv = -1;
int dst_rv = 1; // Non-zero (done), non-negative (error) number
unsigned int i = 0;
do {
const int pending = xlat_pending(s);
const int prefer_dq = (pending > 4);
const int last_src_rv = src_rv;
av_log(avctx, AV_LOG_TRACE, "Pending=%d, src_rv=%d, req_pkt=%d\n", pending, src_rv, s->req_pkt);
// Enqueue another pkt for decode if
// (a) We don't have a lot of stuff in the buffer already OR
// (b) ... we (think we) do but we've failed to get a frame already OR
// (c) We've dequeued a lot of frames without asking for input
src_rv = try_enqueue_src(avctx, s, !(!prefer_dq || i != 0 || s->req_pkt > 2));
// If we got a frame last time or we've already tried to get a frame and
// we have nothing to enqueue then return now. rv will be AVERROR(EAGAIN)
// indicating that we want more input.
// This should mean that once decode starts we enter a stable state where
// we alternately ask for input and produce output
if ((i != 0 || s->req_pkt) && src_rv == NQ_SRC_EMPTY)
break;
if (src_rv == NQ_Q_FULL && last_src_rv == NQ_Q_FULL) {
av_log(avctx, AV_LOG_WARNING, "Poll thinks src Q has space; none found\n");
break;
}
// Try to get a new frame if
// (a) we haven't already got one AND
// (b) enqueue returned a status indicating that decode should be attempted
if (dst_rv != 0 && TRY_DQ(src_rv)) {
// Pick a timeout depending on state
// The pending count isn't completely reliable so it is good enough
// hint that we want a frame but not good enough to require it in
// all cases; however if it has got > 31 that exceeds its margin of
// error so require a frame to prevent ridiculous levels of latency
const int t =
src_rv == NQ_Q_FULL ? -1 :
src_rv == NQ_DRAINING ? 300 :
prefer_dq ? (s->running && pending > 31 ? 100 : 5) : 0;
// Dequeue frame will unref any previous contents of frame
// if it returns success so we don't need an explicit unref
// when discarding
// This returns AVERROR(EAGAIN) on timeout or if
// there is room in the input Q and timeout == -1
dst_rv = ff_v4l2_context_dequeue_frame(&s->capture, frame, t);
// Failure due to no buffer in Q?
if (dst_rv == AVERROR(ENOSPC)) {
// Wait & retry
if ((dst_rv = qbuf_wait(avctx, &s->capture)) == 0) {
dst_rv = ff_v4l2_context_dequeue_frame(&s->capture, frame, t);
}
}
if (dst_rv == 0) {
set_best_effort_pts(avctx, &s->pts_stat, frame);
if (!s->running) {
s->running = 1;
av_log(avctx, AV_LOG_VERBOSE, "Decode running\n");
}
}
if (dst_rv == AVERROR(EAGAIN) && src_rv == NQ_DRAINING) {
av_log(avctx, AV_LOG_WARNING, "Timeout in drain - assume EOF");
dst_rv = AVERROR_EOF;
s->capture.done = 1;
}
else if (dst_rv == AVERROR_EOF && (s->draining || s->capture.done))
av_log(avctx, AV_LOG_DEBUG, "Dequeue EOF: draining=%d, cap.done=%d\n",
s->draining, s->capture.done);
else if (dst_rv && dst_rv != AVERROR(EAGAIN))
av_log(avctx, AV_LOG_ERROR, "Packet dequeue failure: draining=%d, cap.done=%d, err=%d\n",
s->draining, s->capture.done, dst_rv);
}
++i;
if (i >= 256) {
av_log(avctx, AV_LOG_ERROR, "Unexpectedly large retry count: %d\n", i);
src_rv = AVERROR(EIO);
}
// Continue trying to enqueue packets if either
// (a) we succeeded last time OR
// (b) we didn't ret a frame and we can retry the input
} while (src_rv == NQ_OK || (dst_rv == AVERROR(EAGAIN) && RETRY_NQ(src_rv)));
// Ensure that the frame contains nothing if we aren't returning a frame
// (might happen when discarding)
if (dst_rv)
av_frame_unref(frame);
// If we got a frame this time ask for a pkt next time
s->req_pkt = (dst_rv == 0) ? s->req_pkt + 1 : 0;
#if 0
if (dst_rv == 0)
{
static int z = 0;
if (++z > 50) {
av_log(avctx, AV_LOG_ERROR, "Streamoff and die?\n");
ff_v4l2_context_set_status(&s->capture, VIDIOC_STREAMOFF);
return -1;
}
}
#endif
return dst_rv == 0 ? 0 :
src_rv < 0 ? src_rv :
dst_rv < 0 ? dst_rv :
AVERROR(EAGAIN);
}
#if 0
#include <time.h>
static int64_t us_time(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
static int v4l2_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
int ret;
const int64_t now = us_time();
int64_t done;
av_log(avctx, AV_LOG_TRACE, "<<< %s\n", __func__);
ret = v4l2_receive_frame2(avctx, frame);
done = us_time();
av_log(avctx, AV_LOG_TRACE, ">>> %s: rx time=%" PRId64 ", rv=%d\n", __func__, done - now, ret);
return ret;
}
#endif
static uint32_t
avprofile_to_v4l2(const enum AVCodecID codec_id, const int avprofile)
{
switch (codec_id) {
case AV_CODEC_ID_H264:
switch (avprofile) {
case FF_PROFILE_H264_BASELINE:
return V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE;
case FF_PROFILE_H264_CONSTRAINED_BASELINE:
return V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE;
case FF_PROFILE_H264_MAIN:
return V4L2_MPEG_VIDEO_H264_PROFILE_MAIN;
case FF_PROFILE_H264_EXTENDED:
return V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED;
case FF_PROFILE_H264_HIGH:
return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH;
case FF_PROFILE_H264_HIGH_10:
return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10;
case FF_PROFILE_H264_HIGH_10_INTRA:
return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10_INTRA;
case FF_PROFILE_H264_MULTIVIEW_HIGH:
case FF_PROFILE_H264_HIGH_422:
return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422;
case FF_PROFILE_H264_HIGH_422_INTRA:
return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA;
case FF_PROFILE_H264_STEREO_HIGH:
return V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH;
case FF_PROFILE_H264_HIGH_444_PREDICTIVE:
return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE;
case FF_PROFILE_H264_HIGH_444_INTRA:
return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_INTRA;
case FF_PROFILE_H264_CAVLC_444:
return V4L2_MPEG_VIDEO_H264_PROFILE_CAVLC_444_INTRA;
case FF_PROFILE_H264_HIGH_444:
default:
break;
// V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE = 12,
// V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH = 13,
// V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH_INTRA = 14,
// V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH = 16,
// V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_HIGH = 17,
}
break;
case AV_CODEC_ID_MPEG2VIDEO:
case AV_CODEC_ID_MPEG4:
case AV_CODEC_ID_VC1:
case AV_CODEC_ID_VP8:
case AV_CODEC_ID_VP9:
case AV_CODEC_ID_AV1:
// Most profiles are a simple number that matches the V4L2 enum
return avprofile;
default:
break;
}
return ~(uint32_t)0;
}
// This check mirrors Chrome's profile check by testing to see if the profile
// exists as a possible value for the V4L2 profile control
static int
check_profile(AVCodecContext *const avctx, V4L2m2mContext *const s)
{
struct v4l2_queryctrl query_ctrl;
struct v4l2_querymenu query_menu;
uint32_t profile_id;
// An unset profile is almost certainly zero or -99 - do not reject
if (avctx->profile <= 0) {
av_log(avctx, AV_LOG_VERBOSE, "Profile %d <= 0 - check skipped\n", avctx->profile);
return 0;
}
memset(&query_ctrl, 0, sizeof(query_ctrl));
switch (avctx->codec_id) {
case AV_CODEC_ID_MPEG2VIDEO:
profile_id = V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE;
break;
case AV_CODEC_ID_MPEG4:
profile_id = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE;
break;
case AV_CODEC_ID_H264:
profile_id = V4L2_CID_MPEG_VIDEO_H264_PROFILE;
break;
case AV_CODEC_ID_VP8:
profile_id = V4L2_CID_MPEG_VIDEO_VP8_PROFILE;
break;
case AV_CODEC_ID_VP9:
profile_id = V4L2_CID_MPEG_VIDEO_VP9_PROFILE;
break;
#ifdef V4L2_CID_MPEG_VIDEO_AV1_PROFILE
case AV_CODEC_ID_AV1:
profile_id = V4L2_CID_MPEG_VIDEO_AV1_PROFILE;
break;
#endif
default:
av_log(avctx, AV_LOG_VERBOSE, "Can't map profile for codec id %d; profile check skipped\n", avctx->codec_id);
return 0;
}
query_ctrl = (struct v4l2_queryctrl){.id = profile_id};
if (ioctl(s->fd, VIDIOC_QUERYCTRL, &query_ctrl) != 0) {
av_log(avctx, AV_LOG_VERBOSE, "Query profile ctrl (%#x) not supported: assume OK\n", query_ctrl.id);
}
else {
av_log(avctx, AV_LOG_DEBUG, "%s: Control supported: %#x\n", __func__, query_ctrl.id);
query_menu = (struct v4l2_querymenu){
.id = query_ctrl.id,
.index = avprofile_to_v4l2(avctx->codec_id, avctx->profile),
};
if (query_menu.index > query_ctrl.maximum ||
query_menu.index < query_ctrl.minimum ||
ioctl(s->fd, VIDIOC_QUERYMENU, &query_menu) != 0) {
return AVERROR(ENOENT);
}
}
return 0;
};
static int
check_size(AVCodecContext * const avctx, V4L2m2mContext * const s, const uint32_t fcc)
{
unsigned int i;
const uint32_t w = avctx->coded_width;
const uint32_t h = avctx->coded_height;
if (w == 0 || h == 0 || fcc == 0) {
av_log(avctx, AV_LOG_TRACE, "%s: Size %dx%d or fcc %s empty\n", __func__, w, h, av_fourcc2str(fcc));
return 0;
}
if ((s->quirks & FF_V4L2_QUIRK_ENUM_FRAMESIZES_BROKEN) != 0) {
av_log(avctx, AV_LOG_TRACE, "%s: Skipped (quirk): Size %dx%d, fcc %s\n", __func__, w, h, av_fourcc2str(fcc));
return 0;
}
for (i = 0;; ++i) {
struct v4l2_frmsizeenum fs = {
.index = i,
.pixel_format = fcc,
};
while (ioctl(s->fd, VIDIOC_ENUM_FRAMESIZES, &fs) != 0) {
const int err = AVERROR(errno);
if (err == AVERROR(EINTR))
continue;
if (i == 0 && err == AVERROR(ENOTTY)) {
av_log(avctx, AV_LOG_DEBUG, "Framesize enum not supported\n");
return 0;
}
if (err != AVERROR(EINVAL)) {
av_log(avctx, AV_LOG_ERROR, "Failed to enum framesizes: %s", av_err2str(err));
return err;
}
av_log(avctx, AV_LOG_WARNING, "Failed to find Size=%dx%d, fmt=%s in %u frame size enums\n",
w, h, av_fourcc2str(fcc), i);
return err;
}
switch (fs.type) {
case V4L2_FRMSIZE_TYPE_DISCRETE:
av_log(avctx, AV_LOG_TRACE, "%s[%d]: Discrete: %dx%d\n", __func__, i,
fs.discrete.width,fs.discrete.height);
if (w == fs.discrete.width && h == fs.discrete.height)
return 0;
break;
case V4L2_FRMSIZE_TYPE_STEPWISE:
av_log(avctx, AV_LOG_TRACE, "%s[%d]: Stepwise: Min: %dx%d Max: %dx%d, Step: %dx%d\n", __func__, i,
fs.stepwise.min_width, fs.stepwise.min_height,
fs.stepwise.max_width, fs.stepwise.max_height,
fs.stepwise.step_width,fs.stepwise.step_height);
if (w >= fs.stepwise.min_width && w <= fs.stepwise.max_width &&
h >= fs.stepwise.min_height && h <= fs.stepwise.max_height &&
(w - fs.stepwise.min_width) % fs.stepwise.step_width == 0 &&
(h - fs.stepwise.min_height) % fs.stepwise.step_height == 0)
return 0;
break;
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
av_log(avctx, AV_LOG_TRACE, "%s[%d]: Continuous: Min: %dx%d Max: %dx%d, Step: %dx%d\n", __func__, i,
fs.stepwise.min_width, fs.stepwise.min_height,
fs.stepwise.max_width, fs.stepwise.max_height,
fs.stepwise.step_width,fs.stepwise.step_height);
if (w >= fs.stepwise.min_width && w <= fs.stepwise.max_width &&
h >= fs.stepwise.min_height && h <= fs.stepwise.max_height)
return 0;
break;
default:
av_log(avctx, AV_LOG_ERROR, "Unexpected framesize enum: %d", fs.type);
return AVERROR(EINVAL);
}
}
}
static int
get_quirks(AVCodecContext * const avctx, V4L2m2mContext * const s)
{
struct v4l2_capability cap;
memset(&cap, 0, sizeof(cap));
while (ioctl(s->fd, VIDIOC_QUERYCAP, &cap) != 0) {
int err = errno;
if (err == EINTR)
continue;
av_log(avctx, AV_LOG_ERROR, "V4L2: Failed to get capabilities: %s\n", strerror(err));
return AVERROR(err);
}
// Could be made table driven if we have a few more but right now there
// seems no point
// Meson (amlogic) always gives a resolution changed event after output
// streamon and userspace must (re)allocate capture buffers and streamon
// capture to clear the event even if the capture buffers were the right
// size in the first place.
if (strcmp(cap.driver, "meson-vdec") == 0)
s->quirks |= FF_V4L2_QUIRK_REINIT_ALWAYS | FF_V4L2_QUIRK_ENUM_FRAMESIZES_BROKEN;
av_log(avctx, AV_LOG_DEBUG, "Driver '%s': Quirks=%#x\n", cap.driver, s->quirks);
return 0;
}
// This heuristic is for H264 but use for everything
static uint32_t max_coded_size(const AVCodecContext * const avctx)
{
uint32_t wxh = avctx->coded_width * avctx->coded_height;
uint32_t size;
size = wxh * 3 / 2;
// H.264 Annex A table A-1 gives minCR which is either 2 or 4
// unfortunately that doesn't yield an actually useful limit
// and it should be noted that frame 0 is special cased to allow
// a bigger number which really isn't helpful for us. So just pick
// frame_size / 2
size /= 2;
// Add 64k to allow for any overheads and/or encoder hopefulness
// with small WxH
return size + (1 << 16);
}
static void
parse_extradata(AVCodecContext * const avctx, V4L2m2mContext * const s)
{
s->reorder_size = 0;
if (!avctx->extradata || !avctx->extradata_size)
return;
switch (avctx->codec_id) {
#if CONFIG_H264_DECODER
case AV_CODEC_ID_H264:
{
H264ParamSets ps;
int is_avc = 0;
int nal_length_size = 0;
int ret;
memset(&ps, 0, sizeof(ps));
ret = ff_h264_decode_extradata(avctx->extradata, avctx->extradata_size,
&ps, &is_avc, &nal_length_size,
avctx->err_recognition, avctx);
if (ret > 0) {
const SPS * sps = NULL;
unsigned int i;
for (i = 0; i != MAX_SPS_COUNT; ++i) {
if (ps.sps_list[i]) {
sps = ps.sps_list[i];
break;
}
}
if (sps) {
avctx->profile = ff_h264_get_profile(sps);
avctx->level = sps->level_idc;
s->reorder_size = sps->num_reorder_frames;
}
}
ff_h264_ps_uninit(&ps);
break;
}
#endif
#if CONFIG_HEVC_DECODER
case AV_CODEC_ID_HEVC:
{
HEVCParamSets ps;
HEVCSEI sei;
int is_nalff = 0;
int nal_length_size = 0;
int ret;
memset(&ps, 0, sizeof(ps));
memset(&sei, 0, sizeof(sei));
ret = ff_hevc_decode_extradata(avctx->extradata, avctx->extradata_size,
&ps, &sei, &is_nalff, &nal_length_size,
avctx->err_recognition, 0, avctx);
if (ret > 0) {
const HEVCSPS * sps = NULL;
unsigned int i;
for (i = 0; i != HEVC_MAX_SPS_COUNT; ++i) {
if (ps.sps_list[i]) {
sps = ps.sps_list[i];
break;
}
}
if (sps) {
avctx->profile = sps->ptl.general_ptl.profile_idc;
avctx->level = sps->ptl.general_ptl.level_idc;
s->reorder_size = sps->temporal_layer[sps->max_sub_layers - 1].max_dec_pic_buffering;
}
}
ff_hevc_ps_uninit(&ps);
ff_hevc_reset_sei(&sei);
break;
}
#endif
default:
break;
}
}
static int
choose_capture_format(AVCodecContext * const avctx, V4L2m2mContext * const s)
{
const V4L2m2mPriv * const priv = avctx->priv_data;
unsigned int fmts_n;
uint32_t *fmts = ff_v4l2_context_enum_drm_formats(&s->capture, &fmts_n);
enum AVPixelFormat *fmts2 = NULL;
enum AVPixelFormat gf_pix_fmt;
unsigned int i;
unsigned int n = 0;
unsigned int pref_n = 1;
int rv = AVERROR(ENOENT);
if (!fmts)
return AVERROR(ENOENT);
if ((fmts2 = av_malloc(sizeof(*fmts2) * (fmts_n + 3))) == NULL) {
rv = AVERROR(ENOMEM);
goto error;
}
// Filter for formats that are supported by ffmpeg and
// can accomodate the stream size
fmts2[n++] = AV_PIX_FMT_DRM_PRIME;
for (i = 0; i != fmts_n; ++i) {
const enum AVPixelFormat f = ff_v4l2_format_v4l2_to_avfmt(fmts[i], AV_CODEC_ID_RAWVIDEO);
av_log(avctx, AV_LOG_TRACE, "VLC pix %s -> %s\n", av_fourcc2str(fmts[i]), av_get_pix_fmt_name(f));
if (f == AV_PIX_FMT_NONE)
continue;
if (check_size(avctx, s, fmts[i]) != 0)
continue;
if (f == priv->pix_fmt)
pref_n = n;
fmts2[n++] = f;
}
if (n < 2) {
av_log(avctx, AV_LOG_DEBUG, "%s: No usable formats found\n", __func__);
goto error;
}
if (n != 2) {
// ffmpeg.c really only expects one s/w format. It thinks that the
// last format in the list is the s/w format of the h/w format but
// also chooses the first non-h/w format as the preferred s/w format.
// The only way of reconciling this is to dup our preferred format into
// both last & first place :-(
const enum AVPixelFormat t = fmts2[pref_n];
fmts2[pref_n] = fmts2[1];
fmts2[1] = t;
fmts2[n++] = t;
}
fmts2[n] = AV_PIX_FMT_NONE;
gf_pix_fmt = ff_get_format(avctx, fmts2);
av_log(avctx, AV_LOG_DEBUG, "avctx requested=%d (%s) %dx%d; get_format requested=%d (%s)\n",
avctx->pix_fmt, av_get_pix_fmt_name(avctx->pix_fmt),
avctx->coded_width, avctx->coded_height,
gf_pix_fmt, av_get_pix_fmt_name(gf_pix_fmt));
if (gf_pix_fmt == AV_PIX_FMT_NONE)
goto error;
if (gf_pix_fmt == AV_PIX_FMT_DRM_PRIME || avctx->pix_fmt == AV_PIX_FMT_DRM_PRIME) {
avctx->pix_fmt = AV_PIX_FMT_DRM_PRIME;
s->capture.av_pix_fmt = avctx->sw_pix_fmt;
s->output_drm = 1;
}
else {
avctx->pix_fmt = gf_pix_fmt;
s->capture.av_pix_fmt = gf_pix_fmt;
s->output_drm = 0;
}
// Get format converts capture.av_pix_fmt back into a V4L2 format in the context
if ((rv = ff_v4l2_context_get_format(&s->capture, 0)) != 0)
goto error;
rv = ff_v4l2_context_set_format(&s->capture);
error:
av_free(fmts2);
av_free(fmts);
return rv;
}
static av_cold int v4l2_decode_init(AVCodecContext *avctx)
{
V4L2Context *capture, *output;
V4L2m2mContext *s;
V4L2m2mPriv *priv = avctx->priv_data;
int ret;
av_log(avctx, AV_LOG_TRACE, "<<< %s\n", __func__);
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
if (avctx->codec_id == AV_CODEC_ID_H264) {
if (avctx->ticks_per_frame == 1) {
if(avctx->time_base.den < INT_MAX/2) {
avctx->time_base.den *= 2;
} else
avctx->time_base.num /= 2;
}
avctx->ticks_per_frame = 2;
}
FF_ENABLE_DEPRECATION_WARNINGS
#endif
ret = ff_v4l2_m2m_create_context(priv, &s);
if (ret < 0)
return ret;
parse_extradata(avctx, s);
xlat_init(&s->xlat);
pts_stats_init(&s->pts_stat, avctx, "decoder");
capture = &s->capture;
output = &s->output;
/* if these dimensions are invalid (ie, 0 or too small) an event will be raised
* by the v4l2 driver; this event will trigger a full pipeline reconfig and
* the proper values will be retrieved from the kernel driver.
*/
// output->height = capture->height = avctx->coded_height;
// output->width = capture->width = avctx->coded_width;
output->height = capture->height = 0;
output->width = capture->width = 0;
output->av_codec_id = avctx->codec_id;
output->av_pix_fmt = AV_PIX_FMT_NONE;
output->min_buf_size = max_coded_size(avctx);
capture->av_codec_id = AV_CODEC_ID_RAWVIDEO;
capture->av_pix_fmt = avctx->pix_fmt;
capture->min_buf_size = 0;
capture->av_pix_fmt = AV_PIX_FMT_NONE;
s->output_drm = 0;
s->db_ctl = NULL;
if (priv->dmabuf_alloc != NULL && strcmp(priv->dmabuf_alloc, "v4l2") != 0) {
if (strcmp(priv->dmabuf_alloc, "cma") == 0)
s->db_ctl = dmabufs_ctl_new();
else {
av_log(avctx, AV_LOG_ERROR, "Unknown dmabuf alloc method: '%s'\n", priv->dmabuf_alloc);
return AVERROR(EINVAL);
}
if (!s->db_ctl) {
av_log(avctx, AV_LOG_ERROR, "Can't open dmabuf provider '%s'\n", priv->dmabuf_alloc);
return AVERROR(ENOMEM);
}
}
s->device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_DRM);
if (!s->device_ref) {
ret = AVERROR(ENOMEM);
return ret;
}
ret = av_hwdevice_ctx_init(s->device_ref);
if (ret < 0)
return ret;
s->avctx = avctx;
ret = ff_v4l2_m2m_codec_init(priv);
if (ret) {
av_log(avctx, AV_LOG_ERROR, "can't configure decoder\n");
return ret;
}
if (avctx->extradata &&
(ret = copy_extradata(avctx, avctx->extradata, avctx->extradata_size, &s->extdata_data, &s->extdata_size)) != 0) {
av_log(avctx, AV_LOG_ERROR, "Failed to copy extradata from context: %s\n", av_err2str(ret));
#if DUMP_FAILED_EXTRADATA
log_dump(avctx, AV_LOG_INFO, avctx->extradata, avctx->extradata_size);
#endif
return ret;
}
if ((ret = get_quirks(avctx, s)) != 0)
return ret;
if ((ret = check_profile(avctx, s)) != 0) {
av_log(avctx, AV_LOG_WARNING, "Profile %d not supported by decode\n", avctx->profile);
return ret;
}
// Size check done as part of format filtering
if ((ret = choose_capture_format(avctx, s)) != 0)
return ret;
if ((ret = v4l2_prepare_decoder(s)) < 0)
return ret;
return 0;
}
static av_cold int v4l2_decode_close(AVCodecContext *avctx)
{
int rv;
av_log(avctx, AV_LOG_TRACE, "<<< %s\n", __func__);
rv = ff_v4l2_m2m_codec_end(avctx->priv_data);
av_log(avctx, AV_LOG_TRACE, ">>> %s: rv=%d\n", __func__, rv);
return rv;
}
static void v4l2_decode_flush(AVCodecContext *avctx)
{
// An alternatve and more drastic form of flush is to simply do this:
// v4l2_decode_close(avctx);
// v4l2_decode_init(avctx);
// The downside is that this keeps a decoder open until all the frames
// associated with it have been returned. This is a bit wasteful on
// possibly limited h/w resources and fails on a Pi for this reason unless
// more GPU mem is allocated than is the default.
V4L2m2mPriv * const priv = avctx->priv_data;
V4L2m2mContext * const s = priv->context;
V4L2Context * const output = &s->output;
V4L2Context * const capture = &s->capture;
av_log(avctx, AV_LOG_TRACE, "<<< %s: streamon=%d\n", __func__, output->streamon);
// Reflushing everything is benign, quick and avoids having to worry about
// states like EOS processing so don't try to optimize out (having got it
// wrong once)
ff_v4l2_context_set_status(output, VIDIOC_STREAMOFF);
// Clear any buffered input packet
av_packet_unref(&s->buf_pkt);
// Clear a pending EOS
if (ff_v4l2_ctx_eos(capture)) {
// Arguably we could delay this but this is easy and doesn't require
// thought or extra vars
ff_v4l2_context_set_status(capture, VIDIOC_STREAMOFF);
ff_v4l2_context_set_status(capture, VIDIOC_STREAMON);
}
// V4L2 makes no guarantees about whether decoded frames are flushed or not
// so mark all frames we are tracking to be discarded if they appear
xlat_flush(&s->xlat);
// resend extradata
s->extdata_sent = 0;
// clear status vars
s->running = 0;
s->draining = 0;
output->done = 0;
capture->done = 0;
// Stream on will occur when we actually submit a new frame
av_log(avctx, AV_LOG_TRACE, ">>> %s\n", __func__);
}
#define OFFSET(x) offsetof(V4L2m2mPriv, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM
static const AVOption options[] = {
V4L_M2M_DEFAULT_OPTS,
{ "num_capture_buffers", "Number of buffers in the capture context",
OFFSET(num_capture_buffers), AV_OPT_TYPE_INT, {.i64 = 20}, 2, INT_MAX, FLAGS },
{ "pixel_format", "Pixel format to be used by the decoder", OFFSET(pix_fmt), AV_OPT_TYPE_PIXEL_FMT, {.i64 = AV_PIX_FMT_NONE}, AV_PIX_FMT_NONE, AV_PIX_FMT_NB, FLAGS },
{ "dmabuf_alloc", "Dmabuf alloc method", OFFSET(dmabuf_alloc), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
{ NULL},
};
static const AVCodecHWConfigInternal *v4l2_m2m_hw_configs[] = {
HW_CONFIG_INTERNAL(DRM_PRIME),
NULL
};
#define M2MDEC_CLASS(NAME) \
static const AVClass v4l2_m2m_ ## NAME ## _dec_class = { \
.class_name = #NAME "_v4l2m2m_decoder", \
.item_name = av_default_item_name, \
.option = options, \
.version = LIBAVUTIL_VERSION_INT, \
};
#define M2MDEC(NAME, LONGNAME, CODEC, bsf_name) \
M2MDEC_CLASS(NAME) \
const FFCodec ff_ ## NAME ## _v4l2m2m_decoder = { \
.p.name = #NAME "_v4l2m2m" , \
CODEC_LONG_NAME("V4L2 mem2mem " LONGNAME " decoder wrapper"), \
.p.type = AVMEDIA_TYPE_VIDEO, \
.p.id = CODEC , \
.priv_data_size = sizeof(V4L2m2mPriv), \
.p.priv_class = &v4l2_m2m_ ## NAME ## _dec_class, \
.init = v4l2_decode_init, \
FF_CODEC_RECEIVE_FRAME_CB(v4l2_receive_frame), \
.close = v4l2_decode_close, \
.flush = v4l2_decode_flush, \
.bsfs = bsf_name, \
.p.capabilities = AV_CODEC_CAP_HARDWARE | AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AVOID_PROBING, \
.caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE | \
FF_CODEC_CAP_INIT_CLEANUP, \
.p.wrapper_name = "v4l2m2m", \
.p.pix_fmts = (const enum AVPixelFormat[]) { AV_PIX_FMT_DRM_PRIME, \
AV_PIX_FMT_NV12, \
AV_PIX_FMT_YUV420P, \
AV_PIX_FMT_NONE}, \
.hw_configs = v4l2_m2m_hw_configs, \
}
M2MDEC(h264, "H.264", AV_CODEC_ID_H264, "h264_mp4toannexb");
M2MDEC(hevc, "HEVC", AV_CODEC_ID_HEVC, "hevc_mp4toannexb");
M2MDEC(mpeg1, "MPEG1", AV_CODEC_ID_MPEG1VIDEO, NULL);
M2MDEC(mpeg2, "MPEG2", AV_CODEC_ID_MPEG2VIDEO, NULL);
M2MDEC(mpeg4, "MPEG4", AV_CODEC_ID_MPEG4, NULL);
M2MDEC(h263, "H.263", AV_CODEC_ID_H263, NULL);
M2MDEC(vc1 , "VC1", AV_CODEC_ID_VC1, NULL);
M2MDEC(vp8, "VP8", AV_CODEC_ID_VP8, NULL);
M2MDEC(vp9, "VP9", AV_CODEC_ID_VP9, NULL);