/* * Intel MediaSDK QSV codec-independent code * * copyright (c) 2013 Luca Barbato * copyright (c) 2015 Anton Khirnov * * 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 #include #include #include #include "libavutil/common.h" #include "libavutil/fifo.h" #include "libavutil/frame.h" #include "libavutil/hwcontext.h" #include "libavutil/hwcontext_qsv.h" #include "libavutil/mem.h" #include "libavutil/log.h" #include "libavutil/opt.h" #include "libavutil/pixfmt.h" #include "libavutil/time.h" #include "libavutil/imgutils.h" #include "libavutil/film_grain_params.h" #include "libavutil/mastering_display_metadata.h" #include "libavutil/avassert.h" #include "avcodec.h" #include "codec_internal.h" #include "internal.h" #include "decode.h" #include "hwconfig.h" #include "qsv.h" #include "qsv_internal.h" #include "refstruct.h" #if QSV_ONEVPL #include #else #define MFXUnload(a) do { } while(0) #endif static const AVRational mfx_tb = { 1, 90000 }; #define PTS_TO_MFX_PTS(pts, pts_tb) ((pts) == AV_NOPTS_VALUE ? \ MFX_TIMESTAMP_UNKNOWN : pts_tb.num ? \ av_rescale_q(pts, pts_tb, mfx_tb) : pts) #define MFX_PTS_TO_PTS(mfx_pts, pts_tb) ((mfx_pts) == MFX_TIMESTAMP_UNKNOWN ? \ AV_NOPTS_VALUE : pts_tb.num ? \ av_rescale_q(mfx_pts, mfx_tb, pts_tb) : mfx_pts) #define MFX_IMPL_VIA_MASK(impl) (0x0f00 & (impl)) typedef struct QSVAsyncFrame { mfxSyncPoint *sync; QSVFrame *frame; } QSVAsyncFrame; typedef struct QSVContext { // the session used for decoding mfxSession session; mfxVersion ver; mfxHandleType handle_type; // the session we allocated internally, in case the caller did not provide // one QSVSession internal_qs; QSVFramesContext frames_ctx; /** * a linked list of frames currently being used by QSV */ QSVFrame *work_frames; AVFifo *async_fifo; int zero_consume_run; int reinit_flag; enum AVPixelFormat orig_pix_fmt; uint32_t fourcc; mfxFrameInfo frame_info; AVBufferPool *pool; int suggest_pool_size; int initialized; // options set by the caller int async_depth; int iopattern; int gpu_copy; char *load_plugins; mfxExtBuffer **ext_buffers; int nb_ext_buffers; } QSVContext; static const AVCodecHWConfigInternal *const qsv_hw_configs[] = { &(const AVCodecHWConfigInternal) { .public = { .pix_fmt = AV_PIX_FMT_QSV, .methods = AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX | AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, .device_type = AV_HWDEVICE_TYPE_QSV, }, .hwaccel = NULL, }, NULL }; static int qsv_get_continuous_buffer(AVCodecContext *avctx, AVFrame *frame, AVBufferPool *pool) { int ret = 0; ret = ff_decode_frame_props(avctx, frame); if (ret < 0) return ret; frame->width = avctx->width; frame->height = avctx->height; switch (avctx->pix_fmt) { case AV_PIX_FMT_NV12: frame->linesize[0] = FFALIGN(avctx->width, 128); break; case AV_PIX_FMT_P010: case AV_PIX_FMT_P012: case AV_PIX_FMT_YUYV422: frame->linesize[0] = 2 * FFALIGN(avctx->width, 128); break; case AV_PIX_FMT_Y210: case AV_PIX_FMT_VUYX: case AV_PIX_FMT_XV30: case AV_PIX_FMT_Y212: frame->linesize[0] = 4 * FFALIGN(avctx->width, 128); break; case AV_PIX_FMT_XV36: frame->linesize[0] = 8 * FFALIGN(avctx->width, 128); break; default: av_log(avctx, AV_LOG_ERROR, "Unsupported pixel format.\n"); return AVERROR(EINVAL); } frame->buf[0] = av_buffer_pool_get(pool); if (!frame->buf[0]) return AVERROR(ENOMEM); frame->data[0] = frame->buf[0]->data; if (avctx->pix_fmt == AV_PIX_FMT_NV12 || avctx->pix_fmt == AV_PIX_FMT_P010 || avctx->pix_fmt == AV_PIX_FMT_P012) { frame->linesize[1] = frame->linesize[0]; frame->data[1] = frame->data[0] + frame->linesize[0] * FFALIGN(avctx->height, 64); } ret = ff_attach_decode_data(frame); if (ret < 0) return ret; return 0; } static int qsv_init_session(AVCodecContext *avctx, QSVContext *q, mfxSession session, AVBufferRef *hw_frames_ref, AVBufferRef *hw_device_ref) { int ret; mfxIMPL impl; if (q->gpu_copy == MFX_GPUCOPY_ON && !(q->iopattern & MFX_IOPATTERN_OUT_SYSTEM_MEMORY)) { av_log(avctx, AV_LOG_WARNING, "GPU-accelerated memory copy " "only works in system memory mode.\n"); q->gpu_copy = MFX_GPUCOPY_OFF; } if (session) { q->session = session; } else if (hw_frames_ref) { if (q->internal_qs.session) { MFXClose(q->internal_qs.session); q->internal_qs.session = NULL; } av_buffer_unref(&q->frames_ctx.hw_frames_ctx); q->frames_ctx.hw_frames_ctx = av_buffer_ref(hw_frames_ref); if (!q->frames_ctx.hw_frames_ctx) return AVERROR(ENOMEM); ret = ff_qsv_init_session_frames(avctx, &q->internal_qs.session, &q->frames_ctx, q->load_plugins, #if QSV_HAVE_OPAQUE q->iopattern == MFX_IOPATTERN_OUT_OPAQUE_MEMORY, #else 0, #endif q->gpu_copy); if (ret < 0) { av_buffer_unref(&q->frames_ctx.hw_frames_ctx); return ret; } q->session = q->internal_qs.session; } else if (hw_device_ref) { if (q->internal_qs.session) { MFXClose(q->internal_qs.session); q->internal_qs.session = NULL; } ret = ff_qsv_init_session_device(avctx, &q->internal_qs.session, hw_device_ref, q->load_plugins, q->gpu_copy); if (ret < 0) return ret; q->session = q->internal_qs.session; } else { if (!q->internal_qs.session) { ret = ff_qsv_init_internal_session(avctx, &q->internal_qs, q->load_plugins, q->gpu_copy); if (ret < 0) return ret; } q->session = q->internal_qs.session; } if (MFXQueryIMPL(q->session, &impl) == MFX_ERR_NONE) { switch (MFX_IMPL_VIA_MASK(impl)) { case MFX_IMPL_VIA_VAAPI: q->handle_type = MFX_HANDLE_VA_DISPLAY; break; case MFX_IMPL_VIA_D3D11: q->handle_type = MFX_HANDLE_D3D11_DEVICE; break; case MFX_IMPL_VIA_D3D9: q->handle_type = MFX_HANDLE_D3D9_DEVICE_MANAGER; break; default: av_assert0(!"should not reach here"); } } else { av_log(avctx, AV_LOG_ERROR, "Error querying the implementation. \n"); goto fail; } if (MFXQueryVersion(q->session, &q->ver) != MFX_ERR_NONE) { av_log(avctx, AV_LOG_ERROR, "Error querying the session version. \n"); goto fail; } /* make sure the decoder is uninitialized */ MFXVideoDECODE_Close(q->session); return 0; fail: q->session = NULL; if (q->internal_qs.session) { MFXClose(q->internal_qs.session); q->internal_qs.session = NULL; } if (q->internal_qs.loader) { MFXUnload(q->internal_qs.loader); q->internal_qs.loader = NULL; } return AVERROR_EXTERNAL; } static int qsv_decode_preinit(AVCodecContext *avctx, QSVContext *q, enum AVPixelFormat pix_fmt, mfxVideoParam *param) { mfxSession session = NULL; int iopattern = 0; int ret; enum AVPixelFormat pix_fmts[3] = { AV_PIX_FMT_QSV, /* opaque format in case of video memory output */ pix_fmt, /* system memory format obtained from bitstream parser */ AV_PIX_FMT_NONE }; ret = ff_get_format(avctx, pix_fmts); if (ret < 0) { q->orig_pix_fmt = avctx->pix_fmt = AV_PIX_FMT_NONE; return ret; } if (!q->async_fifo) { q->async_fifo = av_fifo_alloc2(q->async_depth, sizeof(QSVAsyncFrame), 0); if (!q->async_fifo) return AVERROR(ENOMEM); } if (avctx->pix_fmt == AV_PIX_FMT_QSV && avctx->hwaccel_context) { AVQSVContext *user_ctx = avctx->hwaccel_context; session = user_ctx->session; iopattern = user_ctx->iopattern; q->ext_buffers = user_ctx->ext_buffers; q->nb_ext_buffers = user_ctx->nb_ext_buffers; } if (avctx->hw_device_ctx && !avctx->hw_frames_ctx && ret == AV_PIX_FMT_QSV) { AVHWFramesContext *hwframes_ctx; AVQSVFramesContext *frames_hwctx; avctx->hw_frames_ctx = av_hwframe_ctx_alloc(avctx->hw_device_ctx); if (!avctx->hw_frames_ctx) { av_log(avctx, AV_LOG_ERROR, "av_hwframe_ctx_alloc failed\n"); return AVERROR(ENOMEM); } hwframes_ctx = (AVHWFramesContext*)avctx->hw_frames_ctx->data; frames_hwctx = hwframes_ctx->hwctx; hwframes_ctx->width = FFALIGN(avctx->coded_width, 32); hwframes_ctx->height = FFALIGN(avctx->coded_height, 32); hwframes_ctx->format = AV_PIX_FMT_QSV; hwframes_ctx->sw_format = avctx->sw_pix_fmt; if (QSV_RUNTIME_VERSION_ATLEAST(q->ver, 2, 9) && q->handle_type != MFX_HANDLE_D3D9_DEVICE_MANAGER) hwframes_ctx->initial_pool_size = 0; else hwframes_ctx->initial_pool_size = q->suggest_pool_size + 16 + avctx->extra_hw_frames; frames_hwctx->frame_type = MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET; ret = av_hwframe_ctx_init(avctx->hw_frames_ctx); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error initializing a QSV frame pool\n"); av_buffer_unref(&avctx->hw_frames_ctx); return ret; } } if (avctx->hw_frames_ctx) { AVHWFramesContext *frames_ctx = (AVHWFramesContext*)avctx->hw_frames_ctx->data; AVQSVFramesContext *frames_hwctx = frames_ctx->hwctx; if (!iopattern) { #if QSV_HAVE_OPAQUE if (frames_hwctx->frame_type & MFX_MEMTYPE_OPAQUE_FRAME) iopattern = MFX_IOPATTERN_OUT_OPAQUE_MEMORY; else if (frames_hwctx->frame_type & MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET) iopattern = MFX_IOPATTERN_OUT_VIDEO_MEMORY; #else if (frames_hwctx->frame_type & MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET) iopattern = MFX_IOPATTERN_OUT_VIDEO_MEMORY; #endif } } if (!iopattern) iopattern = MFX_IOPATTERN_OUT_SYSTEM_MEMORY; q->iopattern = iopattern; ff_qsv_print_iopattern(avctx, q->iopattern, "Decoder"); ret = qsv_init_session(avctx, q, session, avctx->hw_frames_ctx, avctx->hw_device_ctx); if (ret < 0) { av_log(avctx, AV_LOG_ERROR, "Error initializing an MFX session\n"); return ret; } param->IOPattern = q->iopattern; param->AsyncDepth = q->async_depth; param->ExtParam = q->ext_buffers; param->NumExtParam = q->nb_ext_buffers; return 0; } static int qsv_decode_init_context(AVCodecContext *avctx, QSVContext *q, mfxVideoParam *param) { int ret; avctx->width = param->mfx.FrameInfo.CropW; avctx->height = param->mfx.FrameInfo.CropH; avctx->coded_width = param->mfx.FrameInfo.Width; avctx->coded_height = param->mfx.FrameInfo.Height; avctx->level = param->mfx.CodecLevel; avctx->profile = param->mfx.CodecProfile; avctx->field_order = ff_qsv_map_picstruct(param->mfx.FrameInfo.PicStruct); avctx->pix_fmt = ff_qsv_map_fourcc(param->mfx.FrameInfo.FourCC); ret = MFXVideoDECODE_Init(q->session, param); if (ret < 0) return ff_qsv_print_error(avctx, ret, "Error initializing the MFX video decoder"); q->frame_info = param->mfx.FrameInfo; if (!avctx->hw_frames_ctx) { ret = av_image_get_buffer_size(avctx->pix_fmt, FFALIGN(avctx->width, 128), FFALIGN(avctx->height, 64), 1); if (ret < 0) return ret; q->pool = av_buffer_pool_init(ret, av_buffer_allocz); } return 0; } static int qsv_decode_header(AVCodecContext *avctx, QSVContext *q, const AVPacket *avpkt, enum AVPixelFormat pix_fmt, mfxVideoParam *param) { int ret; mfxExtVideoSignalInfo video_signal_info = { 0 }; mfxExtBuffer *header_ext_params[1] = { (mfxExtBuffer *)&video_signal_info }; mfxBitstream bs = { 0 }; if (avpkt->size) { bs.Data = avpkt->data; bs.DataLength = avpkt->size; bs.MaxLength = bs.DataLength; bs.TimeStamp = PTS_TO_MFX_PTS(avpkt->pts, avctx->pkt_timebase); if (avctx->field_order == AV_FIELD_PROGRESSIVE) bs.DataFlag |= MFX_BITSTREAM_COMPLETE_FRAME; } else return AVERROR_INVALIDDATA; if(!q->session) { ret = qsv_decode_preinit(avctx, q, pix_fmt, param); if (ret < 0) return ret; } ret = ff_qsv_codec_id_to_mfx(avctx->codec_id); if (ret < 0) return ret; param->mfx.CodecId = ret; video_signal_info.Header.BufferId = MFX_EXTBUFF_VIDEO_SIGNAL_INFO; video_signal_info.Header.BufferSz = sizeof(video_signal_info); // The SDK doesn't support other ext buffers when calling MFXVideoDECODE_DecodeHeader, // so do not append this buffer to the existent buffer array param->ExtParam = header_ext_params; param->NumExtParam = 1; ret = MFXVideoDECODE_DecodeHeader(q->session, &bs, param); if (MFX_ERR_MORE_DATA == ret) { return AVERROR(EAGAIN); } if (ret < 0) return ff_qsv_print_error(avctx, ret, "Error decoding stream header"); avctx->color_range = video_signal_info.VideoFullRange ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; if (video_signal_info.ColourDescriptionPresent) { avctx->color_primaries = video_signal_info.ColourPrimaries; avctx->color_trc = video_signal_info.TransferCharacteristics; avctx->colorspace = video_signal_info.MatrixCoefficients; } param->ExtParam = q->ext_buffers; param->NumExtParam = q->nb_ext_buffers; if (param->mfx.FrameInfo.FrameRateExtN == 0 || param->mfx.FrameInfo.FrameRateExtD == 0) { param->mfx.FrameInfo.FrameRateExtN = 25; param->mfx.FrameInfo.FrameRateExtD = 1; } #if QSV_VERSION_ATLEAST(1, 34) if (QSV_RUNTIME_VERSION_ATLEAST(q->ver, 1, 34) && avctx->codec_id == AV_CODEC_ID_AV1) param->mfx.FilmGrain = (avctx->export_side_data & AV_CODEC_EXPORT_DATA_FILM_GRAIN) ? 0 : param->mfx.FilmGrain; #endif return 0; } static int alloc_frame(AVCodecContext *avctx, QSVContext *q, QSVFrame *frame) { int ret; if (q->pool) ret = qsv_get_continuous_buffer(avctx, frame->frame, q->pool); else ret = ff_get_buffer(avctx, frame->frame, AV_GET_BUFFER_FLAG_REF); if (ret < 0) return ret; if (frame->frame->format == AV_PIX_FMT_QSV) { frame->surface = *(mfxFrameSurface1*)frame->frame->data[3]; } else { ret = ff_qsv_map_frame_to_surface(frame->frame, &frame->surface); if (ret < 0) { av_log(avctx, AV_LOG_ERROR, "map frame to surface failed.\n"); return ret; } } frame->surface.Info = q->frame_info; if (q->frames_ctx.mids) { ret = ff_qsv_find_surface_idx(&q->frames_ctx, frame); if (ret < 0) return ret; frame->surface.Data.MemId = &q->frames_ctx.mids[ret]; } frame->surface.Data.ExtParam = frame->ext_param; frame->surface.Data.NumExtParam = 0; frame->num_ext_params = 0; frame->dec_info.Header.BufferId = MFX_EXTBUFF_DECODED_FRAME_INFO; frame->dec_info.Header.BufferSz = sizeof(frame->dec_info); ff_qsv_frame_add_ext_param(avctx, frame, (mfxExtBuffer *)&frame->dec_info); #if QSV_VERSION_ATLEAST(1, 34) if (QSV_RUNTIME_VERSION_ATLEAST(q->ver, 1, 34) && avctx->codec_id == AV_CODEC_ID_AV1) { frame->av1_film_grain_param.Header.BufferId = MFX_EXTBUFF_AV1_FILM_GRAIN_PARAM; frame->av1_film_grain_param.Header.BufferSz = sizeof(frame->av1_film_grain_param); frame->av1_film_grain_param.FilmGrainFlags = 0; ff_qsv_frame_add_ext_param(avctx, frame, (mfxExtBuffer *)&frame->av1_film_grain_param); } #endif #if QSV_VERSION_ATLEAST(1, 35) if (QSV_RUNTIME_VERSION_ATLEAST(q->ver, 1, 35) && avctx->codec_id == AV_CODEC_ID_HEVC) { frame->mdcv.Header.BufferId = MFX_EXTBUFF_MASTERING_DISPLAY_COLOUR_VOLUME; frame->mdcv.Header.BufferSz = sizeof(frame->mdcv); // The data in mdcv is valid when this flag is 1 frame->mdcv.InsertPayloadToggle = 0; ff_qsv_frame_add_ext_param(avctx, frame, (mfxExtBuffer *)&frame->mdcv); frame->clli.Header.BufferId = MFX_EXTBUFF_CONTENT_LIGHT_LEVEL_INFO; frame->clli.Header.BufferSz = sizeof(frame->clli); // The data in clli is valid when this flag is 1 frame->clli.InsertPayloadToggle = 0; ff_qsv_frame_add_ext_param(avctx, frame, (mfxExtBuffer *)&frame->clli); } #endif frame->used = 1; return 0; } static void qsv_clear_unused_frames(QSVContext *q) { QSVFrame *cur = q->work_frames; while (cur) { if (cur->used && !cur->surface.Data.Locked && !cur->queued) { cur->used = 0; av_frame_unref(cur->frame); } cur = cur->next; } } static int get_surface(AVCodecContext *avctx, QSVContext *q, mfxFrameSurface1 **surf) { QSVFrame *frame, **last; int ret; qsv_clear_unused_frames(q); frame = q->work_frames; last = &q->work_frames; while (frame) { if (!frame->used) { ret = alloc_frame(avctx, q, frame); if (ret < 0) return ret; *surf = &frame->surface; return 0; } last = &frame->next; frame = frame->next; } frame = av_mallocz(sizeof(*frame)); if (!frame) return AVERROR(ENOMEM); frame->frame = av_frame_alloc(); if (!frame->frame) { av_freep(&frame); return AVERROR(ENOMEM); } *last = frame; ret = alloc_frame(avctx, q, frame); if (ret < 0) return ret; *surf = &frame->surface; return 0; } static QSVFrame *find_frame(QSVContext *q, mfxFrameSurface1 *surf) { QSVFrame *cur = q->work_frames; while (cur) { if (surf == &cur->surface) return cur; cur = cur->next; } return NULL; } #if QSV_VERSION_ATLEAST(1, 34) static int qsv_export_film_grain(AVCodecContext *avctx, mfxExtAV1FilmGrainParam *ext_param, AVFrame *frame) { AVFilmGrainParams *fgp; AVFilmGrainAOMParams *aom; int i; if (!(ext_param->FilmGrainFlags & MFX_FILM_GRAIN_APPLY)) return 0; fgp = av_film_grain_params_create_side_data(frame); if (!fgp) return AVERROR(ENOMEM); fgp->type = AV_FILM_GRAIN_PARAMS_AV1; fgp->seed = ext_param->GrainSeed; aom = &fgp->codec.aom; aom->chroma_scaling_from_luma = !!(ext_param->FilmGrainFlags & MFX_FILM_GRAIN_CHROMA_SCALING_FROM_LUMA); aom->scaling_shift = ext_param->GrainScalingMinus8 + 8; aom->ar_coeff_lag = ext_param->ArCoeffLag; aom->ar_coeff_shift = ext_param->ArCoeffShiftMinus6 + 6; aom->grain_scale_shift = ext_param->GrainScaleShift; aom->overlap_flag = !!(ext_param->FilmGrainFlags & MFX_FILM_GRAIN_OVERLAP); aom->limit_output_range = !!(ext_param->FilmGrainFlags & MFX_FILM_GRAIN_CLIP_TO_RESTRICTED_RANGE); aom->num_y_points = ext_param->NumYPoints; for (i = 0; i < aom->num_y_points; i++) { aom->y_points[i][0] = ext_param->PointY[i].Value; aom->y_points[i][1] = ext_param->PointY[i].Scaling; } aom->num_uv_points[0] = ext_param->NumCbPoints; for (i = 0; i < aom->num_uv_points[0]; i++) { aom->uv_points[0][i][0] = ext_param->PointCb[i].Value; aom->uv_points[0][i][1] = ext_param->PointCb[i].Scaling; } aom->num_uv_points[1] = ext_param->NumCrPoints; for (i = 0; i < aom->num_uv_points[1]; i++) { aom->uv_points[1][i][0] = ext_param->PointCr[i].Value; aom->uv_points[1][i][1] = ext_param->PointCr[i].Scaling; } for (i = 0; i < 24; i++) aom->ar_coeffs_y[i] = ext_param->ArCoeffsYPlus128[i] - 128; for (i = 0; i < 25; i++) { aom->ar_coeffs_uv[0][i] = ext_param->ArCoeffsCbPlus128[i] - 128; aom->ar_coeffs_uv[1][i] = ext_param->ArCoeffsCrPlus128[i] - 128; } aom->uv_mult[0] = ext_param->CbMult; aom->uv_mult[1] = ext_param->CrMult; aom->uv_mult_luma[0] = ext_param->CbLumaMult; aom->uv_mult_luma[1] = ext_param->CrLumaMult; aom->uv_offset[0] = ext_param->CbOffset; aom->uv_offset[1] = ext_param->CrOffset; return 0; } #endif #if QSV_VERSION_ATLEAST(1, 35) static int qsv_export_hdr_side_data(AVCodecContext *avctx, mfxExtMasteringDisplayColourVolume *mdcv, mfxExtContentLightLevelInfo *clli, AVFrame *frame) { int ret; // The SDK re-uses this flag for HDR SEI parsing if (mdcv->InsertPayloadToggle) { AVMasteringDisplayMetadata *mastering; const int mapping[3] = {2, 0, 1}; const int chroma_den = 50000; const int luma_den = 10000; int i; ret = ff_decode_mastering_display_new(avctx, frame, &mastering); if (ret < 0) return ret; if (mastering) { for (i = 0; i < 3; i++) { const int j = mapping[i]; mastering->display_primaries[i][0] = av_make_q(mdcv->DisplayPrimariesX[j], chroma_den); mastering->display_primaries[i][1] = av_make_q(mdcv->DisplayPrimariesY[j], chroma_den); } mastering->white_point[0] = av_make_q(mdcv->WhitePointX, chroma_den); mastering->white_point[1] = av_make_q(mdcv->WhitePointY, chroma_den); mastering->max_luminance = av_make_q(mdcv->MaxDisplayMasteringLuminance, luma_den); mastering->min_luminance = av_make_q(mdcv->MinDisplayMasteringLuminance, luma_den); mastering->has_luminance = 1; mastering->has_primaries = 1; } } // The SDK re-uses this flag for HDR SEI parsing if (clli->InsertPayloadToggle) { AVContentLightMetadata *light; ret = ff_decode_content_light_new(avctx, frame, &light); if (ret < 0) return ret; if (light) { light->MaxCLL = clli->MaxContentLightLevel; light->MaxFALL = clli->MaxPicAverageLightLevel; } } return 0; } #endif static int qsv_decode(AVCodecContext *avctx, QSVContext *q, AVFrame *frame, int *got_frame, const AVPacket *avpkt) { mfxFrameSurface1 *insurf; mfxFrameSurface1 *outsurf; mfxSyncPoint *sync; mfxBitstream bs = { { { 0 } } }; int ret; if (avpkt->size) { bs.Data = avpkt->data; bs.DataLength = avpkt->size; bs.MaxLength = bs.DataLength; bs.TimeStamp = PTS_TO_MFX_PTS(avpkt->pts, avctx->pkt_timebase); if (avctx->field_order == AV_FIELD_PROGRESSIVE) bs.DataFlag |= MFX_BITSTREAM_COMPLETE_FRAME; } sync = av_mallocz(sizeof(*sync)); if (!sync) { av_freep(&sync); return AVERROR(ENOMEM); } do { ret = get_surface(avctx, q, &insurf); if (ret < 0) { av_freep(&sync); return ret; } ret = MFXVideoDECODE_DecodeFrameAsync(q->session, avpkt->size ? &bs : NULL, insurf, &outsurf, sync); if (ret == MFX_WRN_DEVICE_BUSY) av_usleep(500); } while (ret == MFX_WRN_DEVICE_BUSY || ret == MFX_ERR_MORE_SURFACE); if (ret == MFX_ERR_INCOMPATIBLE_VIDEO_PARAM) { q->reinit_flag = 1; av_log(avctx, AV_LOG_DEBUG, "Video parameter change\n"); av_freep(&sync); return 0; } if (ret != MFX_ERR_NONE && ret != MFX_ERR_MORE_DATA && ret != MFX_WRN_VIDEO_PARAM_CHANGED && ret != MFX_ERR_MORE_SURFACE) { av_freep(&sync); return ff_qsv_print_error(avctx, ret, "Error during QSV decoding."); } /* make sure we do not enter an infinite loop if the SDK * did not consume any data and did not return anything */ if (!*sync && !bs.DataOffset) { bs.DataOffset = avpkt->size; ++q->zero_consume_run; if (q->zero_consume_run > 1 && (avpkt->size || ret != MFX_ERR_MORE_DATA)) ff_qsv_print_warning(avctx, ret, "A decode call did not consume any data"); } else { q->zero_consume_run = 0; } if (*sync) { QSVAsyncFrame aframe; QSVFrame *out_frame = find_frame(q, outsurf); if (!out_frame) { av_log(avctx, AV_LOG_ERROR, "The returned surface does not correspond to any frame\n"); av_freep(&sync); return AVERROR_BUG; } out_frame->queued += 1; aframe = (QSVAsyncFrame){ sync, out_frame }; av_fifo_write(q->async_fifo, &aframe, 1); } else { av_freep(&sync); } if ((av_fifo_can_read(q->async_fifo) >= q->async_depth) || (!avpkt->size && av_fifo_can_read(q->async_fifo))) { QSVAsyncFrame aframe; AVFrame *src_frame; av_fifo_read(q->async_fifo, &aframe, 1); aframe.frame->queued -= 1; if (avctx->pix_fmt != AV_PIX_FMT_QSV) { do { ret = MFXVideoCORE_SyncOperation(q->session, *aframe.sync, 1000); } while (ret == MFX_WRN_IN_EXECUTION); } av_freep(&aframe.sync); src_frame = aframe.frame->frame; ret = av_frame_ref(frame, src_frame); if (ret < 0) return ret; outsurf = &aframe.frame->surface; frame->pts = MFX_PTS_TO_PTS(outsurf->Data.TimeStamp, avctx->pkt_timebase); #if QSV_VERSION_ATLEAST(1, 34) if ((avctx->export_side_data & AV_CODEC_EXPORT_DATA_FILM_GRAIN) && QSV_RUNTIME_VERSION_ATLEAST(q->ver, 1, 34) && avctx->codec_id == AV_CODEC_ID_AV1) { ret = qsv_export_film_grain(avctx, &aframe.frame->av1_film_grain_param, frame); if (ret < 0) return ret; } #endif #if QSV_VERSION_ATLEAST(1, 35) if (QSV_RUNTIME_VERSION_ATLEAST(q->ver, 1, 35) && avctx->codec_id == AV_CODEC_ID_HEVC) { ret = qsv_export_hdr_side_data(avctx, &aframe.frame->mdcv, &aframe.frame->clli, frame); if (ret < 0) return ret; } #endif frame->repeat_pict = outsurf->Info.PicStruct & MFX_PICSTRUCT_FRAME_TRIPLING ? 4 : outsurf->Info.PicStruct & MFX_PICSTRUCT_FRAME_DOUBLING ? 2 : outsurf->Info.PicStruct & MFX_PICSTRUCT_FIELD_REPEATED ? 1 : 0; frame->flags |= AV_FRAME_FLAG_TOP_FIELD_FIRST * !!(outsurf->Info.PicStruct & MFX_PICSTRUCT_FIELD_TFF); frame->flags |= AV_FRAME_FLAG_INTERLACED * !(outsurf->Info.PicStruct & MFX_PICSTRUCT_PROGRESSIVE); frame->pict_type = ff_qsv_map_pictype(aframe.frame->dec_info.FrameType); //Key frame is IDR frame is only suitable for H264. For HEVC, IRAPs are key frames. if (avctx->codec_id == AV_CODEC_ID_H264) { if (aframe.frame->dec_info.FrameType & MFX_FRAMETYPE_IDR) frame->flags |= AV_FRAME_FLAG_KEY; else frame->flags &= ~AV_FRAME_FLAG_KEY; } /* update the surface properties */ if (avctx->pix_fmt == AV_PIX_FMT_QSV) ((mfxFrameSurface1*)frame->data[3])->Info = outsurf->Info; *got_frame = 1; } return bs.DataOffset; } static void qsv_decode_close_qsvcontext(QSVContext *q) { QSVFrame *cur = q->work_frames; if (q->session) MFXVideoDECODE_Close(q->session); if (q->async_fifo) { QSVAsyncFrame aframe; while (av_fifo_read(q->async_fifo, &aframe, 1) >= 0) av_freep(&aframe.sync); av_fifo_freep2(&q->async_fifo); } while (cur) { q->work_frames = cur->next; av_frame_free(&cur->frame); av_freep(&cur); cur = q->work_frames; } ff_qsv_close_internal_session(&q->internal_qs); av_buffer_unref(&q->frames_ctx.hw_frames_ctx); ff_refstruct_unref(&q->frames_ctx.mids); av_buffer_pool_uninit(&q->pool); } static int qsv_process_data(AVCodecContext *avctx, QSVContext *q, AVFrame *frame, int *got_frame, const AVPacket *pkt) { int ret; mfxVideoParam param = { 0 }; enum AVPixelFormat pix_fmt = AV_PIX_FMT_NV12; if (!pkt->size) return qsv_decode(avctx, q, frame, got_frame, pkt); /* TODO: flush delayed frames on reinit */ // sw_pix_fmt, coded_width/height should be set for ff_get_format(), // assume sw_pix_fmt is NV12 and coded_width/height to be 1280x720, // the assumption may be not corret but will be updated after header decoded if not true. if (q->orig_pix_fmt != AV_PIX_FMT_NONE) pix_fmt = q->orig_pix_fmt; if (!avctx->coded_width) avctx->coded_width = 1280; if (!avctx->coded_height) avctx->coded_height = 720; /* decode zero-size pkt to flush the buffered pkt before reinit */ if (q->reinit_flag) { AVPacket zero_pkt = {0}; ret = qsv_decode(avctx, q, frame, got_frame, &zero_pkt); if (ret < 0 || *got_frame) return ret; } if (q->reinit_flag || !q->session || !q->initialized) { mfxFrameAllocRequest request; memset(&request, 0, sizeof(request)); q->reinit_flag = 0; ret = qsv_decode_header(avctx, q, pkt, pix_fmt, ¶m); if (ret < 0) { if (ret == AVERROR(EAGAIN)) av_log(avctx, AV_LOG_VERBOSE, "More data is required to decode header\n"); else av_log(avctx, AV_LOG_ERROR, "Error decoding header\n"); goto reinit_fail; } param.IOPattern = q->iopattern; q->orig_pix_fmt = avctx->pix_fmt = pix_fmt = ff_qsv_map_fourcc(param.mfx.FrameInfo.FourCC); avctx->coded_width = param.mfx.FrameInfo.Width; avctx->coded_height = param.mfx.FrameInfo.Height; ret = MFXVideoDECODE_QueryIOSurf(q->session, ¶m, &request); if (ret < 0) return ff_qsv_print_error(avctx, ret, "Error querying IO surface"); q->suggest_pool_size = request.NumFrameSuggested; ret = qsv_decode_preinit(avctx, q, pix_fmt, ¶m); if (ret < 0) goto reinit_fail; q->initialized = 0; } if (!q->initialized) { ret = qsv_decode_init_context(avctx, q, ¶m); if (ret < 0) goto reinit_fail; q->initialized = 1; } return qsv_decode(avctx, q, frame, got_frame, pkt); reinit_fail: q->orig_pix_fmt = avctx->pix_fmt = AV_PIX_FMT_NONE; return ret; } enum LoadPlugin { LOAD_PLUGIN_NONE, LOAD_PLUGIN_HEVC_SW, LOAD_PLUGIN_HEVC_HW, }; typedef struct QSVDecContext { AVClass *class; QSVContext qsv; int load_plugin; AVFifo *packet_fifo; AVPacket buffer_pkt; } QSVDecContext; static void qsv_clear_buffers(QSVDecContext *s) { AVPacket pkt; while (av_fifo_read(s->packet_fifo, &pkt, 1) >= 0) av_packet_unref(&pkt); av_packet_unref(&s->buffer_pkt); } static av_cold int qsv_decode_close(AVCodecContext *avctx) { QSVDecContext *s = avctx->priv_data; qsv_decode_close_qsvcontext(&s->qsv); qsv_clear_buffers(s); av_fifo_freep2(&s->packet_fifo); return 0; } static av_cold int qsv_decode_init(AVCodecContext *avctx) { QSVDecContext *s = avctx->priv_data; int ret; const char *uid = NULL; if (avctx->codec_id == AV_CODEC_ID_VP8) { uid = "f622394d8d87452f878c51f2fc9b4131"; } else if (avctx->codec_id == AV_CODEC_ID_VP9) { uid = "a922394d8d87452f878c51f2fc9b4131"; } else if (avctx->codec_id == AV_CODEC_ID_HEVC && s->load_plugin != LOAD_PLUGIN_NONE) { static const char * const uid_hevcdec_sw = "15dd936825ad475ea34e35f3f54217a6"; static const char * const uid_hevcdec_hw = "33a61c0b4c27454ca8d85dde757c6f8e"; if (s->qsv.load_plugins[0]) { av_log(avctx, AV_LOG_WARNING, "load_plugins is not empty, but load_plugin is not set to 'none'." "The load_plugin value will be ignored.\n"); } else { if (s->load_plugin == LOAD_PLUGIN_HEVC_SW) uid = uid_hevcdec_sw; else uid = uid_hevcdec_hw; } } if (uid) { av_freep(&s->qsv.load_plugins); s->qsv.load_plugins = av_strdup(uid); if (!s->qsv.load_plugins) return AVERROR(ENOMEM); } s->qsv.orig_pix_fmt = AV_PIX_FMT_NV12; s->packet_fifo = av_fifo_alloc2(1, sizeof(AVPacket), AV_FIFO_FLAG_AUTO_GROW); if (!s->packet_fifo) { ret = AVERROR(ENOMEM); goto fail; } if (!avctx->pkt_timebase.num) av_log(avctx, AV_LOG_WARNING, "Invalid pkt_timebase, passing timestamps as-is.\n"); return 0; fail: qsv_decode_close(avctx); return ret; } static int qsv_decode_frame(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *avpkt) { QSVDecContext *s = avctx->priv_data; int ret; /* buffer the input packet */ if (avpkt->size) { AVPacket input_ref; ret = av_packet_ref(&input_ref, avpkt); if (ret < 0) return ret; av_fifo_write(s->packet_fifo, &input_ref, 1); } /* process buffered data */ while (!*got_frame) { /* prepare the input data */ if (s->buffer_pkt.size <= 0) { /* no more data */ if (!av_fifo_can_read(s->packet_fifo)) return avpkt->size ? avpkt->size : qsv_process_data(avctx, &s->qsv, frame, got_frame, avpkt); /* in progress of reinit, no read from fifo and keep the buffer_pkt */ if (!s->qsv.reinit_flag) { av_packet_unref(&s->buffer_pkt); av_fifo_read(s->packet_fifo, &s->buffer_pkt, 1); } } ret = qsv_process_data(avctx, &s->qsv, frame, got_frame, &s->buffer_pkt); if (ret < 0){ if (ret == AVERROR(EAGAIN)) ret = 0; /* Drop buffer_pkt when failed to decode the packet. Otherwise, the decoder will keep decoding the failure packet. */ av_packet_unref(&s->buffer_pkt); return ret; } if (s->qsv.reinit_flag) continue; s->buffer_pkt.size -= ret; s->buffer_pkt.data += ret; } return avpkt->size; } static void qsv_decode_flush(AVCodecContext *avctx) { QSVDecContext *s = avctx->priv_data; qsv_clear_buffers(s); s->qsv.orig_pix_fmt = AV_PIX_FMT_NONE; s->qsv.initialized = 0; } #define OFFSET(x) offsetof(QSVDecContext, x) #define VD AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM #define DEFINE_QSV_DECODER_WITH_OPTION(x, X, bsf_name, opt) \ static const AVClass x##_qsv_class = { \ .class_name = #x "_qsv", \ .item_name = av_default_item_name, \ .option = opt, \ .version = LIBAVUTIL_VERSION_INT, \ }; \ const FFCodec ff_##x##_qsv_decoder = { \ .p.name = #x "_qsv", \ CODEC_LONG_NAME(#X " video (Intel Quick Sync Video acceleration)"), \ .priv_data_size = sizeof(QSVDecContext), \ .p.type = AVMEDIA_TYPE_VIDEO, \ .p.id = AV_CODEC_ID_##X, \ .init = qsv_decode_init, \ FF_CODEC_DECODE_CB(qsv_decode_frame), \ .flush = qsv_decode_flush, \ .close = qsv_decode_close, \ .bsfs = bsf_name, \ .p.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_DR1 | AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_HYBRID, \ .p.priv_class = &x##_qsv_class, \ .hw_configs = qsv_hw_configs, \ .p.wrapper_name = "qsv", \ .caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE, \ }; \ #define DEFINE_QSV_DECODER(x, X, bsf_name) DEFINE_QSV_DECODER_WITH_OPTION(x, X, bsf_name, options) #if CONFIG_HEVC_QSV_DECODER static const AVOption hevc_options[] = { { "async_depth", "Internal parallelization depth, the higher the value the higher the latency.", OFFSET(qsv.async_depth), AV_OPT_TYPE_INT, { .i64 = ASYNC_DEPTH_DEFAULT }, 1, INT_MAX, VD }, { "load_plugin", "A user plugin to load in an internal session", OFFSET(load_plugin), AV_OPT_TYPE_INT, { .i64 = LOAD_PLUGIN_HEVC_HW }, LOAD_PLUGIN_NONE, LOAD_PLUGIN_HEVC_HW, VD, .unit = "load_plugin" }, { "none", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = LOAD_PLUGIN_NONE }, 0, 0, VD, .unit = "load_plugin" }, { "hevc_sw", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = LOAD_PLUGIN_HEVC_SW }, 0, 0, VD, .unit = "load_plugin" }, { "hevc_hw", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = LOAD_PLUGIN_HEVC_HW }, 0, 0, VD, .unit = "load_plugin" }, { "load_plugins", "A :-separate list of hexadecimal plugin UIDs to load in an internal session", OFFSET(qsv.load_plugins), AV_OPT_TYPE_STRING, { .str = "" }, 0, 0, VD }, { "gpu_copy", "A GPU-accelerated copy between video and system memory", OFFSET(qsv.gpu_copy), AV_OPT_TYPE_INT, { .i64 = MFX_GPUCOPY_DEFAULT }, MFX_GPUCOPY_DEFAULT, MFX_GPUCOPY_OFF, VD, .unit = "gpu_copy"}, { "default", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = MFX_GPUCOPY_DEFAULT }, 0, 0, VD, .unit = "gpu_copy"}, { "on", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = MFX_GPUCOPY_ON }, 0, 0, VD, .unit = "gpu_copy"}, { "off", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = MFX_GPUCOPY_OFF }, 0, 0, VD, .unit = "gpu_copy"}, { NULL }, }; DEFINE_QSV_DECODER_WITH_OPTION(hevc, HEVC, "hevc_mp4toannexb", hevc_options) #endif static const AVOption options[] = { { "async_depth", "Internal parallelization depth, the higher the value the higher the latency.", OFFSET(qsv.async_depth), AV_OPT_TYPE_INT, { .i64 = ASYNC_DEPTH_DEFAULT }, 1, INT_MAX, VD }, { "gpu_copy", "A GPU-accelerated copy between video and system memory", OFFSET(qsv.gpu_copy), AV_OPT_TYPE_INT, { .i64 = MFX_GPUCOPY_DEFAULT }, MFX_GPUCOPY_DEFAULT, MFX_GPUCOPY_OFF, VD, .unit = "gpu_copy"}, { "default", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = MFX_GPUCOPY_DEFAULT }, 0, 0, VD, .unit = "gpu_copy"}, { "on", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = MFX_GPUCOPY_ON }, 0, 0, VD, .unit = "gpu_copy"}, { "off", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = MFX_GPUCOPY_OFF }, 0, 0, VD, .unit = "gpu_copy"}, { NULL }, }; #if CONFIG_H264_QSV_DECODER DEFINE_QSV_DECODER(h264, H264, "h264_mp4toannexb") #endif #if CONFIG_MPEG2_QSV_DECODER DEFINE_QSV_DECODER(mpeg2, MPEG2VIDEO, NULL) #endif #if CONFIG_VC1_QSV_DECODER DEFINE_QSV_DECODER(vc1, VC1, NULL) #endif #if CONFIG_MJPEG_QSV_DECODER DEFINE_QSV_DECODER(mjpeg, MJPEG, NULL) #endif #if CONFIG_VP8_QSV_DECODER DEFINE_QSV_DECODER(vp8, VP8, NULL) #endif #if CONFIG_VP9_QSV_DECODER DEFINE_QSV_DECODER(vp9, VP9, NULL) #endif #if CONFIG_AV1_QSV_DECODER DEFINE_QSV_DECODER(av1, AV1, NULL) #endif