433 lines
15 KiB
C
433 lines
15 KiB
C
/*
|
|
* 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.h"
|
|
#include "decode.h"
|
|
#include "hevc/hevcdec.h"
|
|
#include "hwaccel_internal.h"
|
|
#include "hwconfig.h"
|
|
#include "internal.h"
|
|
|
|
#include "v4l2_fmt.h"
|
|
#include "v4l2_request_hevc.h"
|
|
|
|
#include "libavutil/hwcontext_drm.h"
|
|
#include "libavutil/mem.h"
|
|
#include "libavutil/pixdesc.h"
|
|
|
|
#include "v4l2_req_devscan.h"
|
|
#include "v4l2_req_dmabufs.h"
|
|
#include "v4l2_req_pollqueue.h"
|
|
#include "v4l2_req_media.h"
|
|
#include "v4l2_req_utils.h"
|
|
|
|
static size_t bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8)
|
|
{
|
|
const size_t wxh = w * h;
|
|
size_t bits_alloc;
|
|
|
|
/* Annex A gives a min compression of 2 @ lvl 3.1
|
|
* (wxh <= 983040) and min 4 thereafter but avoid
|
|
* the odity of 983041 having a lower limit than
|
|
* 983040.
|
|
* Multiply by 3/2 for 4:2:0
|
|
*/
|
|
bits_alloc = wxh < 983040 ? wxh * 3 / 4 :
|
|
wxh < 983040 * 2 ? 983040 * 3 / 4 :
|
|
wxh * 3 / 8;
|
|
/* Allow for bit depth */
|
|
bits_alloc += (bits_alloc * bits_minus8) / 8;
|
|
/* Add a few bytes (16k) for overhead */
|
|
bits_alloc += 0x4000;
|
|
return bits_alloc;
|
|
}
|
|
|
|
int ff_v4l2_request_start_frame(AVCodecContext *avctx,
|
|
av_unused const uint8_t *buffer,
|
|
av_unused uint32_t size)
|
|
{
|
|
V4L2RequestPrivHEVC * const priv = avctx->internal->hwaccel_priv_data;
|
|
V4L2RequestContextHEVC *const ctx = priv->cctx;
|
|
return ctx->fns->start_frame(avctx, ctx, buffer, size);
|
|
}
|
|
|
|
int ff_v4l2_request_decode_slice(AVCodecContext *avctx, const uint8_t *buffer, uint32_t size)
|
|
{
|
|
V4L2RequestPrivHEVC * const priv = avctx->internal->hwaccel_priv_data;
|
|
V4L2RequestContextHEVC *const ctx = priv->cctx;
|
|
return ctx->fns->decode_slice(avctx, ctx, buffer, size);
|
|
}
|
|
|
|
int ff_v4l2_request_end_frame(AVCodecContext *avctx)
|
|
{
|
|
V4L2RequestPrivHEVC * const priv = avctx->internal->hwaccel_priv_data;
|
|
V4L2RequestContextHEVC *const ctx = priv->cctx;
|
|
return ctx->fns->end_frame(avctx, ctx);
|
|
}
|
|
|
|
void ff_v4l2_request_abort_frame(AVCodecContext * const avctx)
|
|
{
|
|
V4L2RequestPrivHEVC * const priv = avctx->internal->hwaccel_priv_data;
|
|
V4L2RequestContextHEVC *const ctx = priv->cctx;
|
|
ctx->fns->abort_frame(avctx, ctx);
|
|
}
|
|
|
|
int ff_v4l2_request_frame_params(AVCodecContext *avctx, AVBufferRef *hw_frames_ctx)
|
|
{
|
|
V4L2RequestPrivHEVC * const priv = avctx->internal->hwaccel_priv_data;
|
|
V4L2RequestContextHEVC *const ctx = priv->cctx;
|
|
return ctx->fns->frame_params(avctx, ctx, hw_frames_ctx);
|
|
}
|
|
|
|
int ff_v4l2_request_alloc_frame(AVCodecContext * avctx, AVFrame *frame)
|
|
{
|
|
V4L2RequestPrivHEVC * const priv = avctx->internal->hwaccel_priv_data;
|
|
V4L2RequestContextHEVC *const ctx = priv->cctx;
|
|
return ctx->fns->alloc_frame(avctx, ctx, frame);
|
|
}
|
|
|
|
|
|
static void
|
|
cctx_free(void * v, uint8_t * data)
|
|
{
|
|
V4L2RequestContextHEVC *const ctx = (V4L2RequestContextHEVC *)data;
|
|
|
|
if (ctx->fns && ctx->fns->uninit)
|
|
ctx->fns->uninit(ctx);
|
|
|
|
mediabufs_ctl_unref(&ctx->mbufs);
|
|
media_pool_delete(&ctx->mpool);
|
|
pollqueue_unref(&ctx->pq);
|
|
dmabufs_ctl_unref(&ctx->dbufs);
|
|
devscan_delete(&ctx->devscan);
|
|
|
|
decode_q_uninit(&ctx->decode_q);
|
|
|
|
av_free(ctx);
|
|
}
|
|
|
|
int ff_v4l2_request_uninit(AVCodecContext *avctx)
|
|
{
|
|
V4L2RequestPrivHEVC * const priv = avctx->internal->hwaccel_priv_data;
|
|
|
|
av_log(avctx, AV_LOG_DEBUG, "<<< %s\n", __func__);
|
|
|
|
if (priv->cctx != NULL) {
|
|
V4L2RequestContextHEVC *const ctx = priv->cctx;
|
|
|
|
decode_q_wait(&ctx->decode_q, NULL); // Wait for all other threads to be out of decode
|
|
mediabufs_stream_wait_dst_done(ctx->mbufs); // Now wait for processing to finish
|
|
|
|
priv->cctx = NULL;
|
|
av_buffer_unref(&priv->cctx_buf);
|
|
}
|
|
|
|
// if (avctx->hw_frames_ctx) {
|
|
// AVHWFramesContext *hwfc = (AVHWFramesContext*)avctx->hw_frames_ctx->data;
|
|
// av_buffer_pool_flush(hwfc->pool);
|
|
// }
|
|
return 0;
|
|
}
|
|
|
|
static int dst_fmt_accept_cb(void * v, const struct v4l2_fmtdesc *fmtdesc)
|
|
{
|
|
const int bit_depth = *(int *)v;
|
|
|
|
// SAND is currently confised as to whether it is s/w or hardware
|
|
// *** Current usage is probably wrong - it shouldn't be a h/w fmt
|
|
if (fmtdesc->pixelformat == V4L2_PIX_FMT_NV12_COL128 ||
|
|
fmtdesc->pixelformat == V4L2_PIX_FMT_NV12_COL128M) {
|
|
return bit_depth == 8;
|
|
}
|
|
if (fmtdesc->pixelformat == V4L2_PIX_FMT_NV12_10_COL128 ||
|
|
fmtdesc->pixelformat == V4L2_PIX_FMT_NV12_10_COL128M) {
|
|
return bit_depth == 10;
|
|
}
|
|
else {
|
|
const enum AVPixelFormat fmt = ff_v4l2_format_v4l2_to_avfmt(fmtdesc->pixelformat, AV_CODEC_ID_RAWVIDEO);
|
|
const AVPixFmtDescriptor * const desc = av_pix_fmt_desc_get(fmt);
|
|
|
|
if (fmt == AV_PIX_FMT_NONE || desc == NULL)
|
|
return 0;
|
|
|
|
return bit_depth == desc->comp[0].depth;
|
|
}
|
|
}
|
|
|
|
int ff_v4l2_request_init(AVCodecContext *avctx,
|
|
const struct v4l2_req_decode_fns * const * const try_fns,
|
|
const int width, const int height, const int bit_depth,
|
|
const int dst_buffers)
|
|
{
|
|
V4L2RequestPrivHEVC * const priv = avctx->internal->hwaccel_priv_data;
|
|
V4L2RequestContextHEVC * ctx;
|
|
int ret;
|
|
const struct decdev * decdev;
|
|
const uint32_t src_pix_fmt = try_fns[0]->src_pix_fmt_v4l2; // Assuming constant for all APIs but avoiding V4L2 includes
|
|
size_t src_size;
|
|
enum mediabufs_memory src_memtype;
|
|
enum mediabufs_memory dst_memtype;
|
|
|
|
av_log(avctx, AV_LOG_DEBUG, "<<< %s\n", __func__);
|
|
|
|
if ((ctx = av_mallocz(sizeof(*ctx))) == NULL) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to allocate context");
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
if ((priv->cctx_buf = av_buffer_create((uint8_t*)ctx, sizeof(*ctx), cctx_free, NULL, 0)) == NULL) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to allocate context buffer");
|
|
av_free(ctx);
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
priv->cctx = ctx;
|
|
|
|
decode_q_init(&ctx->decode_q);
|
|
|
|
if ((ret = devscan_build(avctx, &ctx->devscan)) != 0) {
|
|
av_log(avctx, AV_LOG_WARNING, "Failed to find any V4L2 devices\n");
|
|
ret = AVERROR(-ret);
|
|
goto fail0;
|
|
}
|
|
ret = AVERROR(ENOMEM); // Assume mem fail by default for these
|
|
|
|
if ((decdev = devscan_find(ctx->devscan, src_pix_fmt)) == NULL)
|
|
{
|
|
av_log(avctx, AV_LOG_WARNING, "Failed to find a device for %s\n", try_fns[0]->name);
|
|
ret = AVERROR(ENODEV);
|
|
goto fail0;
|
|
}
|
|
av_log(avctx, AV_LOG_DEBUG, "Trying V4L2 devices: %s,%s\n",
|
|
decdev_media_path(decdev), decdev_video_path(decdev));
|
|
|
|
if ((ctx->pq = pollqueue_new()) == NULL) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to create pollqueue\n");
|
|
goto fail1;
|
|
}
|
|
|
|
if ((ctx->mpool = media_pool_new(decdev_media_path(decdev), ctx->pq, 4)) == NULL) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to create media pool\n");
|
|
goto fail2;
|
|
}
|
|
|
|
if ((ctx->mbufs = mediabufs_ctl_new(avctx, decdev_video_path(decdev), ctx->pq)) == NULL) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to create media controls\n");
|
|
goto fail3;
|
|
}
|
|
|
|
// Version test for functional Pi5 HEVC iommu.
|
|
// rpivid kernel patch was merged in 6.1.57
|
|
// *** Remove when it is unlikely that there are any broken kernels left
|
|
if (mediabufs_ctl_driver_version(ctx->mbufs) >= MEDIABUFS_DRIVER_VERSION(6,1,57))
|
|
ctx->dbufs = dmabufs_ctl_new_vidbuf_cached();
|
|
else
|
|
ctx->dbufs = dmabufs_ctl_new();
|
|
|
|
if (ctx->dbufs == NULL) {
|
|
av_log(avctx, AV_LOG_DEBUG, "Unable to open dmabufs - try mmap buffers\n");
|
|
src_memtype = MEDIABUFS_MEMORY_MMAP;
|
|
dst_memtype = MEDIABUFS_MEMORY_MMAP;
|
|
}
|
|
else {
|
|
av_log(avctx, AV_LOG_DEBUG, "Dmabufs opened - try dmabuf buffers\n");
|
|
src_memtype = MEDIABUFS_MEMORY_DMABUF;
|
|
dst_memtype = MEDIABUFS_MEMORY_DMABUF;
|
|
}
|
|
|
|
// Ask for an initial bitbuf size of max size / 4
|
|
// We will realloc if we need more
|
|
// Must use sps->h/w as avctx contains cropped size
|
|
retry_src_memtype:
|
|
src_size = bit_buf_size(width, height, bit_depth - 8);
|
|
if (src_memtype == MEDIABUFS_MEMORY_DMABUF && mediabufs_src_resizable(ctx->mbufs))
|
|
src_size /= 4;
|
|
// Kludge for conformance tests which break Annex A limits
|
|
else if (src_size < 0x40000)
|
|
src_size = 0x40000;
|
|
|
|
if (mediabufs_src_fmt_set(ctx->mbufs, decdev_src_type(decdev), src_pix_fmt,
|
|
width, height, src_size)) {
|
|
char tbuf1[5];
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to set source format: %s %dx%d\n", strfourcc(tbuf1, src_pix_fmt), width, height);
|
|
goto fail4;
|
|
}
|
|
|
|
if (mediabufs_src_chk_memtype(ctx->mbufs, src_memtype)) {
|
|
if (src_memtype == MEDIABUFS_MEMORY_DMABUF) {
|
|
src_memtype = MEDIABUFS_MEMORY_MMAP;
|
|
goto retry_src_memtype;
|
|
}
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to get src memory type\n");
|
|
goto fail4;
|
|
}
|
|
|
|
for (const struct v4l2_req_decode_fns * const * tf = try_fns; (ctx->fns = *tf) != NULL; ++tf)
|
|
if (ctx->fns->probe(avctx, ctx) == 0)
|
|
break;
|
|
if (ctx->fns == NULL) {
|
|
av_log(avctx, AV_LOG_ERROR, "No %s version probed successfully\n", try_fns[0]->name);
|
|
ret = AVERROR(EINVAL);
|
|
goto fail4;
|
|
}
|
|
av_log(avctx, AV_LOG_DEBUG, "%s probed successfully: driver v %#x\n",
|
|
ctx->fns->name, mediabufs_ctl_driver_version(ctx->mbufs));
|
|
|
|
if (mediabufs_dst_fmt_set(ctx->mbufs, width, height, dst_fmt_accept_cb, (void*)&bit_depth)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to set destination format: %dx%d %dbit\n", width, height, bit_depth);
|
|
goto fail4;
|
|
}
|
|
|
|
if (mediabufs_src_pool_create(ctx->mbufs, ctx->dbufs, 6, src_memtype)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to create source pool\n");
|
|
goto fail4;
|
|
}
|
|
|
|
{
|
|
unsigned int dst_slots = dst_buffers +
|
|
avctx->thread_count + (avctx->extra_hw_frames > 0 ? avctx->extra_hw_frames : 6);
|
|
av_log(avctx, AV_LOG_DEBUG, "Slots=%d: Reordering=%d, threads=%d, hw+=%d\n", dst_slots,
|
|
dst_buffers,
|
|
avctx->thread_count, avctx->extra_hw_frames);
|
|
|
|
if (mediabufs_dst_chk_memtype(ctx->mbufs, dst_memtype)) {
|
|
if (dst_memtype != MEDIABUFS_MEMORY_DMABUF) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to get dst memory type\n");
|
|
goto fail4;
|
|
}
|
|
av_log(avctx, AV_LOG_DEBUG, "Dst DMABUF not supported - trying mmap\n");
|
|
dst_memtype = MEDIABUFS_MEMORY_MMAP;
|
|
}
|
|
|
|
// extra_hw_frames is -1 if unset
|
|
if (mediabufs_dst_slots_create(ctx->mbufs, dst_slots, (avctx->extra_hw_frames > 0), dst_memtype)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to create destination slots\n");
|
|
goto fail4;
|
|
}
|
|
}
|
|
|
|
if (mediabufs_stream_on(ctx->mbufs)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed stream on\n");
|
|
goto fail4;
|
|
}
|
|
|
|
if ((ret = ff_decode_get_hw_frames_ctx(avctx, AV_HWDEVICE_TYPE_DRM)) != 0) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to create frame ctx\n");
|
|
goto fail4;
|
|
}
|
|
|
|
if ((ret = ctx->fns->set_controls(avctx, ctx)) != 0) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed set controls\n");
|
|
goto fail5;
|
|
}
|
|
|
|
// Set our s/w format
|
|
avctx->sw_pix_fmt = ((AVHWFramesContext *)avctx->hw_frames_ctx->data)->sw_format;
|
|
|
|
av_log(avctx, AV_LOG_INFO, "Hwaccel %s; devices: %s,%s; buffers: src %s, dst %s; swfmt=%s\n",
|
|
ctx->fns->name,
|
|
decdev_media_path(decdev), decdev_video_path(decdev),
|
|
mediabufs_memory_name(src_memtype), mediabufs_memory_name(dst_memtype),
|
|
av_get_pix_fmt_name(avctx->sw_pix_fmt));
|
|
|
|
return 0;
|
|
|
|
fail5:
|
|
av_buffer_unref(&avctx->hw_frames_ctx);
|
|
fail4:
|
|
fail3:
|
|
fail2:
|
|
fail1:
|
|
fail0:
|
|
priv->cctx = NULL;
|
|
av_buffer_unref(&priv->cctx_buf);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
ff_v4l2_request_update_thread_context(AVCodecContext *dst, const AVCodecContext *src)
|
|
{
|
|
V4L2RequestPrivHEVC * const spriv = src->internal->hwaccel_priv_data;
|
|
V4L2RequestPrivHEVC * const dpriv = dst->internal->hwaccel_priv_data;
|
|
int rv;
|
|
|
|
av_log(dst, AV_LOG_DEBUG, "<<< %s (%s)\n", __func__, dpriv->cctx_buf ? "old" : "new");
|
|
|
|
if ((rv = av_buffer_replace(&dpriv->cctx_buf, spriv->cctx_buf)) != 0)
|
|
return rv;
|
|
|
|
dpriv->cctx = spriv->cctx;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
ff_v4l2_request_free_frame_priv(FFRefStructOpaque hwctx, void *data)
|
|
{
|
|
}
|
|
|
|
static int v4l2_request_hevc_init(AVCodecContext *avctx)
|
|
{
|
|
const HEVCContext *h = avctx->priv_data;
|
|
const HEVCPPS * const pps = h->pps;
|
|
const HEVCSPS * const sps = pps->sps;
|
|
|
|
const struct v4l2_req_decode_fns * const try_fns[] = {
|
|
&V2(ff_v4l2_req_hevc, 4),
|
|
#if CONFIG_V4L2_REQ_HEVC_VX
|
|
&V2(ff_v4l2_req_hevc, 3),
|
|
&V2(ff_v4l2_req_hevc, 2),
|
|
&V2(ff_v4l2_req_hevc, 1),
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
// Give up immediately if this is something that we have no code to deal with
|
|
if (sps->chroma_format_idc != 1) {
|
|
av_log(avctx, AV_LOG_WARNING, "chroma_format_idc(%d) != 1: Not implemented\n", sps->chroma_format_idc);
|
|
return AVERROR_PATCHWELCOME;
|
|
}
|
|
if (!(sps->bit_depth == 10 || sps->bit_depth == 8) ||
|
|
sps->bit_depth != sps->bit_depth_chroma) {
|
|
av_log(avctx, AV_LOG_WARNING, "Bit depth Y:%d C:%d: Not implemented\n", sps->bit_depth, sps->bit_depth_chroma);
|
|
return AVERROR_PATCHWELCOME;
|
|
}
|
|
|
|
return ff_v4l2_request_init(avctx, try_fns, sps->width, sps->height, sps->bit_depth,
|
|
sps->temporal_layer[sps->max_sub_layers - 1].max_dec_pic_buffering);
|
|
}
|
|
|
|
const FFHWAccel ff_hevc_v4l2request_hwaccel = {
|
|
.p = {
|
|
.name = "hevc_v4l2request",
|
|
.type = AVMEDIA_TYPE_VIDEO,
|
|
.id = AV_CODEC_ID_HEVC,
|
|
.pix_fmt = AV_PIX_FMT_DRM_PRIME,
|
|
},
|
|
.alloc_frame = ff_v4l2_request_alloc_frame,
|
|
.start_frame = ff_v4l2_request_start_frame,
|
|
.decode_slice = ff_v4l2_request_decode_slice,
|
|
.end_frame = ff_v4l2_request_end_frame,
|
|
.abort_frame = ff_v4l2_request_abort_frame,
|
|
.init = v4l2_request_hevc_init,
|
|
.uninit = ff_v4l2_request_uninit,
|
|
.free_frame_priv = ff_v4l2_request_free_frame_priv,
|
|
.frame_priv_data_size = 128,
|
|
.update_thread_context = ff_v4l2_request_update_thread_context,
|
|
.priv_data_size = sizeof(V4L2RequestPrivHEVC),
|
|
.frame_params = ff_v4l2_request_frame_params,
|
|
.caps_internal = HWACCEL_CAP_ASYNC_SAFE | HWACCEL_CAP_THREAD_SAFE,
|
|
};
|