/* * 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 <mfx/mfxvideo.h> #include <stdlib.h> #include "libavutil/dict.h" #include "libavutil/mem.h" #include "libavutil/opt.h" #include "libavcodec/qsv.h" #include "ffmpeg.h" typedef struct QSVContext { OutputStream *ost; mfxSession session; mfxExtOpaqueSurfaceAlloc opaque_alloc; AVBufferRef *opaque_surfaces_buf; uint8_t *surface_used; mfxFrameSurface1 **surface_ptrs; int nb_surfaces; mfxExtBuffer *ext_buffers[1]; } QSVContext; static void buffer_release(void *opaque, uint8_t *data) { *(uint8_t*)opaque = 0; } static int qsv_get_buffer(AVCodecContext *s, AVFrame *frame, int flags) { InputStream *ist = s->opaque; QSVContext *qsv = ist->hwaccel_ctx; int i; for (i = 0; i < qsv->nb_surfaces; i++) { if (qsv->surface_used[i]) continue; frame->buf[0] = av_buffer_create((uint8_t*)qsv->surface_ptrs[i], sizeof(*qsv->surface_ptrs[i]), buffer_release, &qsv->surface_used[i], 0); if (!frame->buf[0]) return AVERROR(ENOMEM); frame->data[3] = (uint8_t*)qsv->surface_ptrs[i]; qsv->surface_used[i] = 1; return 0; } return AVERROR(ENOMEM); } static int init_opaque_surf(QSVContext *qsv) { AVQSVContext *hwctx_enc = qsv->ost->enc_ctx->hwaccel_context; mfxFrameSurface1 *surfaces; int i; qsv->nb_surfaces = hwctx_enc->nb_opaque_surfaces; qsv->opaque_surfaces_buf = av_buffer_ref(hwctx_enc->opaque_surfaces); qsv->surface_ptrs = av_mallocz_array(qsv->nb_surfaces, sizeof(*qsv->surface_ptrs)); qsv->surface_used = av_mallocz_array(qsv->nb_surfaces, sizeof(*qsv->surface_used)); if (!qsv->opaque_surfaces_buf || !qsv->surface_ptrs || !qsv->surface_used) return AVERROR(ENOMEM); surfaces = (mfxFrameSurface1*)qsv->opaque_surfaces_buf->data; for (i = 0; i < qsv->nb_surfaces; i++) qsv->surface_ptrs[i] = surfaces + i; qsv->opaque_alloc.Out.Surfaces = qsv->surface_ptrs; qsv->opaque_alloc.Out.NumSurface = qsv->nb_surfaces; qsv->opaque_alloc.Out.Type = hwctx_enc->opaque_alloc_type; qsv->opaque_alloc.Header.BufferId = MFX_EXTBUFF_OPAQUE_SURFACE_ALLOCATION; qsv->opaque_alloc.Header.BufferSz = sizeof(qsv->opaque_alloc); qsv->ext_buffers[0] = (mfxExtBuffer*)&qsv->opaque_alloc; return 0; } static void qsv_uninit(AVCodecContext *s) { InputStream *ist = s->opaque; QSVContext *qsv = ist->hwaccel_ctx; av_freep(&qsv->ost->enc_ctx->hwaccel_context); av_freep(&s->hwaccel_context); av_buffer_unref(&qsv->opaque_surfaces_buf); av_freep(&qsv->surface_used); av_freep(&qsv->surface_ptrs); av_freep(&qsv); } int qsv_init(AVCodecContext *s) { InputStream *ist = s->opaque; QSVContext *qsv = ist->hwaccel_ctx; AVQSVContext *hwctx_dec; int ret; if (!qsv) { av_log(NULL, AV_LOG_ERROR, "QSV transcoding is not initialized. " "-hwaccel qsv should only be used for one-to-one QSV transcoding " "with no filters.\n"); return AVERROR_BUG; } ret = init_opaque_surf(qsv); if (ret < 0) return ret; hwctx_dec = av_qsv_alloc_context(); if (!hwctx_dec) return AVERROR(ENOMEM); hwctx_dec->session = qsv->session; hwctx_dec->iopattern = MFX_IOPATTERN_OUT_OPAQUE_MEMORY; hwctx_dec->ext_buffers = qsv->ext_buffers; hwctx_dec->nb_ext_buffers = FF_ARRAY_ELEMS(qsv->ext_buffers); av_freep(&s->hwaccel_context); s->hwaccel_context = hwctx_dec; ist->hwaccel_get_buffer = qsv_get_buffer; ist->hwaccel_uninit = qsv_uninit; return 0; } static mfxIMPL choose_implementation(const InputStream *ist) { static const struct { const char *name; mfxIMPL impl; } impl_map[] = { { "auto", MFX_IMPL_AUTO }, { "sw", MFX_IMPL_SOFTWARE }, { "hw", MFX_IMPL_HARDWARE }, { "auto_any", MFX_IMPL_AUTO_ANY }, { "hw_any", MFX_IMPL_HARDWARE_ANY }, { "hw2", MFX_IMPL_HARDWARE2 }, { "hw3", MFX_IMPL_HARDWARE3 }, { "hw4", MFX_IMPL_HARDWARE4 }, }; mfxIMPL impl = MFX_IMPL_AUTO_ANY; int i; if (ist->hwaccel_device) { for (i = 0; i < FF_ARRAY_ELEMS(impl_map); i++) if (!strcmp(ist->hwaccel_device, impl_map[i].name)) { impl = impl_map[i].impl; break; } if (i == FF_ARRAY_ELEMS(impl_map)) impl = strtol(ist->hwaccel_device, NULL, 0); } return impl; } int qsv_transcode_init(OutputStream *ost) { InputStream *ist; const enum AVPixelFormat *pix_fmt; AVDictionaryEntry *e; const AVOption *opt; int flags = 0; int err, i; QSVContext *qsv = NULL; AVQSVContext *hwctx = NULL; mfxIMPL impl; mfxVersion ver = { { 3, 1 } }; /* check if the encoder supports QSV */ if (!ost->enc->pix_fmts) return 0; for (pix_fmt = ost->enc->pix_fmts; *pix_fmt != AV_PIX_FMT_NONE; pix_fmt++) if (*pix_fmt == AV_PIX_FMT_QSV) break; if (*pix_fmt == AV_PIX_FMT_NONE) return 0; if (strcmp(ost->avfilter, "null") || ost->source_index < 0) return 0; /* check if the decoder supports QSV and the output only goes to this stream */ ist = input_streams[ost->source_index]; if (ist->nb_filters || ist->hwaccel_id != HWACCEL_QSV || !ist->dec || !ist->dec->pix_fmts) return 0; for (pix_fmt = ist->dec->pix_fmts; *pix_fmt != AV_PIX_FMT_NONE; pix_fmt++) if (*pix_fmt == AV_PIX_FMT_QSV) break; if (*pix_fmt == AV_PIX_FMT_NONE) return 0; for (i = 0; i < nb_output_streams; i++) if (output_streams[i] != ost && output_streams[i]->source_index == ost->source_index) return 0; av_log(NULL, AV_LOG_VERBOSE, "Setting up QSV transcoding\n"); qsv = av_mallocz(sizeof(*qsv)); hwctx = av_qsv_alloc_context(); if (!qsv || !hwctx) goto fail; impl = choose_implementation(ist); err = MFXInit(impl, &ver, &qsv->session); if (err != MFX_ERR_NONE) { av_log(NULL, AV_LOG_ERROR, "Error initializing an MFX session: %d\n", err); goto fail; } e = av_dict_get(ost->encoder_opts, "flags", NULL, 0); opt = av_opt_find(ost->enc_ctx, "flags", NULL, 0, 0); if (e && opt) av_opt_eval_flags(ost->enc_ctx, opt, e->value, &flags); qsv->ost = ost; hwctx->session = qsv->session; hwctx->iopattern = MFX_IOPATTERN_IN_OPAQUE_MEMORY; hwctx->opaque_alloc = 1; hwctx->nb_opaque_surfaces = 16; ost->hwaccel_ctx = qsv; ost->enc_ctx->hwaccel_context = hwctx; ost->enc_ctx->pix_fmt = AV_PIX_FMT_QSV; ist->hwaccel_ctx = qsv; ist->dec_ctx->pix_fmt = AV_PIX_FMT_QSV; ist->resample_pix_fmt = AV_PIX_FMT_QSV; return 0; fail: av_freep(&hwctx); av_freep(&qsv); return AVERROR_UNKNOWN; }