diff options
author | Hendrik Leppkes <h.leppkes@gmail.com> | 2015-10-22 17:18:02 +0300 |
---|---|---|
committer | Hendrik Leppkes <h.leppkes@gmail.com> | 2015-10-22 17:18:02 +0300 |
commit | dd8a4b0f8c39803b6a33f4b4bc836cfc66222370 (patch) | |
tree | 32fb5e4b29968caef33ecd449d25288604556dfc | |
parent | 866a4174dba066780c7be1ebdaa0152161cdf3e0 (diff) | |
parent | fb472e1a11a4e0caed2c3c91da01ea8e35d9e3f8 (diff) |
Merge commit 'fb472e1a11a4e0caed2c3c91da01ea8e35d9e3f8'
* commit 'fb472e1a11a4e0caed2c3c91da01ea8e35d9e3f8':
avconv: add support for Intel QSV-accelerated transcoding
Merged-by: Hendrik Leppkes <h.leppkes@gmail.com>
-rw-r--r-- | Changelog | 1 | ||||
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | doc/ffmpeg.texi | 24 | ||||
-rw-r--r-- | ffmpeg.c | 5 | ||||
-rw-r--r-- | ffmpeg.h | 5 | ||||
-rw-r--r-- | ffmpeg_opt.c | 3 | ||||
-rw-r--r-- | ffmpeg_qsv.c | 268 |
7 files changed, 307 insertions, 0 deletions
@@ -23,6 +23,7 @@ version <next>: - genh, vag, ads, msf & svag demuxer - zscale filter - wve demuxer +- zero-copy Intel QSV transcoding in ffmpeg version 2.8: @@ -35,6 +35,7 @@ ifndef CONFIG_VIDEOTOOLBOX OBJS-ffmpeg-$(CONFIG_VDA) += ffmpeg_videotoolbox.o endif OBJS-ffmpeg-$(CONFIG_VIDEOTOOLBOX) += ffmpeg_videotoolbox.o +OBJS-ffmpeg-$(CONFIG_LIBMFX) += ffmpeg_qsv.o OBJS-ffserver += ffserver_config.o TESTTOOLS = audiogen videogen rotozoom tiny_psnr tiny_ssim base64 diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi index de496185b7..76ea238463 100644 --- a/doc/ffmpeg.texi +++ b/doc/ffmpeg.texi @@ -674,6 +674,16 @@ Use VDPAU (Video Decode and Presentation API for Unix) hardware acceleration. @item dxva2 Use DXVA2 (DirectX Video Acceleration) hardware acceleration. + +@item qsv +Use the Intel QuickSync Video acceleration for video transcoding. + +Unlike most other values, this option does not enable accelerated decoding (that +is used automatically whenever a qsv decoder is selected), but accelerated +transcoding, without copying the frames into the system memory. + +For it to work, both the decoder and the encoder must support QSV acceleration +and no filters must be used. @end table This option has no effect if the selected hwaccel is not available or not @@ -700,6 +710,20 @@ is not specified, the value of the @var{DISPLAY} environment variable is used @item dxva2 For DXVA2, this option should contain the number of the display adapter to use. If this option is not specified, the default adapter is used. + +@item qsv +For QSV, this option corresponds to the valus of MFX_IMPL_* . Allowed values +are: +@table @option +@item auto +@item sw +@item hw +@item auto_any +@item hw_any +@item hw2 +@item hw3 +@item hw4 +@end table @end table @item -hwaccels @@ -3012,6 +3012,11 @@ static int transcode_init(void) set_encoder_id(output_files[ost->file_index], ost); +#if CONFIG_LIBMFX + if (qsv_transcode_init(ost)) + exit_program(1); +#endif + if (!ost->filter && (enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO || enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO)) { @@ -64,6 +64,7 @@ enum HWAccelID { HWACCEL_DXVA2, HWACCEL_VDA, HWACCEL_VIDEOTOOLBOX, + HWACCEL_QSV, }; typedef struct HWAccel { @@ -414,6 +415,8 @@ typedef struct OutputStream { int last_droped; int last_nb0_frames[3]; + void *hwaccel_ctx; + /* video only */ AVRational frame_rate; int force_fps; @@ -567,5 +570,7 @@ int vdpau_init(AVCodecContext *s); int dxva2_init(AVCodecContext *s); int vda_init(AVCodecContext *s); int videotoolbox_init(AVCodecContext *s); +int qsv_init(AVCodecContext *s); +int qsv_transcode_init(OutputStream *ost); #endif /* FFMPEG_H */ diff --git a/ffmpeg_opt.c b/ffmpeg_opt.c index 7139114e62..0da1ffd1eb 100644 --- a/ffmpeg_opt.c +++ b/ffmpeg_opt.c @@ -79,6 +79,9 @@ const HWAccel hwaccels[] = { #if CONFIG_VIDEOTOOLBOX { "videotoolbox", videotoolbox_init, HWACCEL_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX }, #endif +#if CONFIG_LIBMFX + { "qsv", qsv_init, HWACCEL_QSV, AV_PIX_FMT_QSV }, +#endif { 0 }, }; diff --git a/ffmpeg_qsv.c b/ffmpeg_qsv.c new file mode 100644 index 0000000000..95a2351602 --- /dev/null +++ b/ffmpeg_qsv.c @@ -0,0 +1,268 @@ +/* + * 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; +} |