From 3b5a7bdccd5ed6b4189f596549fb300e3d3fd6b1 Mon Sep 17 00:00:00 2001 From: agathah Date: Fri, 24 Oct 2014 15:19:22 +0800 Subject: [PATCH] Add nvenc support --- configure | 9 + doc/examples/Makefile | 1 + doc/examples/libnvenc.c | 177 ++++++++ ffmpeg.c.rej | 11 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/libnvenc.c | 391 ++++++++++++++++ libavcodec/nvenc.h | 132 ++++++ libavcodec/nvencoder.c | 847 +++++++++++++++++++++++++++++++++++ libavcodec/nvencoder.h | 69 +++ libavcodec/nvencoder_utils.c | 328 ++++++++++++++ libavcodec/nvencoder_utils.h | 13 + libavformat/matroskaenc.c | 2 +- libavformat/movenc.c | 6 +- 14 files changed, 1984 insertions(+), 4 deletions(-) create mode 100644 doc/examples/libnvenc.c create mode 100644 ffmpeg.c.rej create mode 100644 libavcodec/libnvenc.c create mode 100644 libavcodec/nvenc.h create mode 100644 libavcodec/nvencoder.c create mode 100644 libavcodec/nvencoder.h create mode 100644 libavcodec/nvencoder_utils.c create mode 100644 libavcodec/nvencoder_utils.h diff --git a/configure b/configure index 5b16af9..28cd118 100644 --- a/configure +++ b/configure @@ -221,6 +221,7 @@ External library support: --enable-libmp3lame enable MP3 encoding via libmp3lame [no] --enable-libnut enable NUT (de)muxing via libnut, native (de)muxer exists [no] + --enable-libnvenc enable NVIDIA NVENC encoder [no] --enable-libopencore-amrnb enable AMR-NB de/encoding via libopencore-amrnb [no] --enable-libopencore-amrwb enable AMR-WB decoding via libopencore-amrwb [no] --enable-libopencv enable video filtering via libopencv [no] @@ -1352,6 +1353,7 @@ EXTERNAL_LIBRARY_LIST=" libmodplug libmp3lame libnut + libnvenc libopencore_amrnb libopencore_amrwb libopencv @@ -2338,6 +2340,7 @@ libilbc_encoder_deps="libilbc" libmodplug_demuxer_deps="libmodplug" libmp3lame_encoder_deps="libmp3lame" libmp3lame_encoder_select="audio_frame_queue" +libnvenc_encoder_deps="libnvenc" libopencore_amrnb_decoder_deps="libopencore_amrnb" libopencore_amrnb_encoder_deps="libopencore_amrnb" libopencore_amrnb_encoder_select="audio_frame_queue" @@ -4328,6 +4331,7 @@ die_license_disabled nonfree libaacplus die_license_disabled nonfree libfaac enabled gpl && die_license_disabled_gpl nonfree libfdk_aac enabled gpl && die_license_disabled_gpl nonfree openssl +enabled gpl && die_license_disabled_gpl nonfree libnvenc die_license_disabled version3 libopencore_amrnb die_license_disabled version3 libopencore_amrwb @@ -4839,6 +4843,11 @@ enabled libilbc && require libilbc ilbc.h WebRtcIlbcfix_InitDecode -li enabled libmodplug && require_pkg_config libmodplug libmodplug/modplug.h ModPlug_Load enabled libmp3lame && require "libmp3lame >= 3.98.3" lame/lame.h lame_set_VBR_quality -lmp3lame enabled libnut && require libnut libnut.h nut_demuxer_init -lnut +enabled libnvenc && { { check_header nvEncodeAPI.h || die "ERROR: nvEncodeAPI.h header not found"; } && + { check_header cuda.h || die "ERROR: cuda.h header not found"; } && + { { check_lib2 "windows.h" LoadLibrary; } || + { check_lib2 "dlfcn.h" dlopen -ldl; } || + die "ERROR: LoadLibrary/dlopen not found for avisynth"; } } enabled libopencore_amrnb && require libopencore_amrnb opencore-amrnb/interf_dec.h Decoder_Interface_init -lopencore-amrnb enabled libopencore_amrwb && require libopencore_amrwb opencore-amrwb/dec_if.h D_IF_init -lopencore-amrwb enabled libopencv && require_pkg_config opencv opencv/cxcore.h cvCreateImageHeader diff --git a/doc/examples/Makefile b/doc/examples/Makefile index 07251fe..195deaa 100644 --- a/doc/examples/Makefile +++ b/doc/examples/Makefile @@ -19,6 +19,7 @@ EXAMPLES= avio_reading \ filtering_audio \ metadata \ muxing \ + libnvenc \ remuxing \ resampling_audio \ scaling_video \ diff --git a/doc/examples/libnvenc.c b/doc/examples/libnvenc.c new file mode 100644 index 0000000..ffc68b8 --- /dev/null +++ b/doc/examples/libnvenc.c @@ -0,0 +1,177 @@ +#ifdef HAVE_AV_CONFIG_H +#undef HAVE_AV_CONFIG_H +#endif + +#if _WIN32 +#define __STDC_CONSTANT_MACROS +#endif + +#include +#include +#include + +#include +#include +#include + +int main(int argc, char* argv[]) +{ + char* inputfile = NULL; + char* preset = "default"; + bool zerolatency = false; + + for(int i = 1; i < argc; i++) { + if(strcmp(argv[i], "-input") == 0) { + inputfile = argv[++i]; + } + else if(strcmp(argv[i], "-preset") == 0) { + preset = argv[++i]; + } + else if(strcmp(argv[i], "-zerolatency") == 0) { + zerolatency = true; + } + } + + if(inputfile == NULL) { + printf("Usage: libnvenc_sample -input your.input -preset [default]/slow/fast\n"); + return 0; + } + + //ffmpeg decoder + av_register_all(); + //init decode context + AVFormatContext *fmt_ctx = NULL; + AVCodecContext *dec_ctx = NULL; + + if(avformat_open_input(&fmt_ctx, inputfile, NULL, NULL) < 0) + { + printf("can't open the file %s\n", inputfile); + return -1; + } + + if(avformat_find_stream_info(fmt_ctx, NULL)<0) + { + printf("can't find suitable codec parameters\n"); + return -1; + } + + AVStream *vst_dec; + AVCodec *dec = NULL; + int ret; + int video_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); + if (video_stream_idx < 0) { + printf("Could not find video stream in input file!\n"); + return -1; + } + else { + vst_dec = fmt_ctx->streams[video_stream_idx]; + + dec_ctx = vst_dec->codec; + dec = avcodec_find_decoder(dec_ctx->codec_id); + + if (!dec) { + fprintf(stderr, "Failed to find %s codec\n", + av_get_media_type_string(AVMEDIA_TYPE_VIDEO)); + return -1; + } + + if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) { + fprintf(stderr, "Failed to open %s codec\n", + av_get_media_type_string(AVMEDIA_TYPE_VIDEO)); + return -1; + } + } + + // encode context + AVFormatContext* oc; + const char* filename = "out.mp4"; + avformat_alloc_output_context2(&oc, NULL, NULL, filename); + if (!oc) { + printf("FFMPEG: avformat_alloc_context error\n"); + return -1; + } + + if ((ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE)) < 0) { + fprintf(stderr, "Could not open '%s'\n", filename); + return -1; + } + + AVCodec* enc = avcodec_find_encoder_by_name("libnvenc"); + if(!enc) { + printf("Could not find libnvenc\n"); + printf("Please run ./configure --enable-libnvenc and make ffmpeg again\n"); + return -1; + } + AVStream* vst_enc = avformat_new_stream(oc, enc); + if (!vst_enc) { + printf("FFMPEG: Could not alloc video stream"); + return -1; + } + + AVCodecContext* enc_ctx = vst_enc->codec; + + double bitrate = (double)5000000 * (dec_ctx->width * dec_ctx->height) / (1280*720); + enc_ctx->codec_id = enc->id; + enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO; + enc_ctx->width = dec_ctx->width; + enc_ctx->height = dec_ctx->height; + enc_ctx->pix_fmt = dec_ctx->pix_fmt; + enc_ctx->time_base.num = dec_ctx->time_base.num; + enc_ctx->time_base.den = dec_ctx->time_base.den/2; + enc_ctx->bit_rate = bitrate; + enc_ctx->flags |= (oc->oformat->flags & AVFMT_GLOBALHEADER) ? CODEC_FLAG_GLOBAL_HEADER : 0; + av_opt_set(enc_ctx->priv_data, "preset", preset, 0); // "fast" = HP, "slow" = HQ, default = LOW_LATENCY_DEFAULT + if(zerolatency) { + //use LOW_LATENCY preset + av_opt_set(enc_ctx->priv_data, "tune", "zerolatency", 0); + } + + ret = avcodec_open2(enc_ctx, enc, NULL); + if (ret < 0) { + printf("could not open codec\n"); + return -1; + } + ret = avformat_write_header(oc, NULL); + + AVPacket dec_pkt; + av_init_packet(&dec_pkt); + dec_pkt.data = NULL; + dec_pkt.size = 0; + + AVFrame *frame = avcodec_alloc_frame(); + int got_frame = 0; + while(av_read_frame(fmt_ctx, &dec_pkt) >= 0) { + if(dec_pkt.stream_index == video_stream_idx) { + if(avcodec_decode_video2(dec_ctx, frame, &got_frame, &dec_pkt) < 0) { + printf("Error decoding frames!\n"); + return -1; + } + if(got_frame) { + AVPacket enc_pkt; + int got_pkt = 0; + av_init_packet(&enc_pkt); + enc_pkt.data = NULL; + enc_pkt.size = 0; + if(avcodec_encode_video2(vst_enc->codec, &enc_pkt, frame, &got_pkt) < 0) { + printf("Error encoding frames!\n"); + return -1; + } + + if(got_pkt) { + av_write_frame(oc, &enc_pkt); + } + + av_free_packet(&enc_pkt); + } + } + av_free_packet(&dec_pkt); + } + avcodec_free_frame(&frame); + av_write_trailer(oc); + + avcodec_close(dec_ctx); + avcodec_close(enc_ctx); + avformat_close_input(&fmt_ctx); + + return 0; +} \ No newline at end of file diff --git a/ffmpeg.c.rej b/ffmpeg.c.rej new file mode 100644 index 0000000..5ec1785 --- /dev/null +++ b/ffmpeg.c.rej @@ -0,0 +1,11 @@ +--- ffmpeg.c ++++ ffmpeg.c +@@ -2644,7 +2644,7 @@ + ost->frame_aspect_ratio.num ? // overridden by the -aspect cli option + av_mul_q(ost->frame_aspect_ratio, (AVRational){ codec->height, codec->width }) : + ost->filter->filter->inputs[0]->sample_aspect_ratio; +- if (!strncmp(ost->enc->name, "libx264", 7) && ++ if ((!strncmp(ost->enc->name, "libx264", 7) || !strncmp(ost->enc->name, "libnvenc", 8)) && + codec->pix_fmt == AV_PIX_FMT_NONE && + ost->filter->filter->inputs[0]->format != AV_PIX_FMT_YUV420P) + av_log(NULL, AV_LOG_WARNING, diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 3d70313..e6d6c86 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -730,6 +730,7 @@ OBJS-$(CONFIG_LIBGSM_MS_ENCODER) += libgsmenc.o OBJS-$(CONFIG_LIBILBC_DECODER) += libilbc.o OBJS-$(CONFIG_LIBILBC_ENCODER) += libilbc.o OBJS-$(CONFIG_LIBMP3LAME_ENCODER) += libmp3lame.o mpegaudiodecheader.o +OBJS-$(CONFIG_LIBNVENC_ENCODER) += libnvenc.o nvencoder.o nvencoder_utils.o OBJS-$(CONFIG_LIBOPENCORE_AMRNB_DECODER) += libopencore-amr.o OBJS-$(CONFIG_LIBOPENCORE_AMRNB_ENCODER) += libopencore-amr.o OBJS-$(CONFIG_LIBOPENCORE_AMRWB_DECODER) += libopencore-amr.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 7650543..915c507 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -506,6 +506,7 @@ void avcodec_register_all(void) REGISTER_ENCDEC (LIBGSM_MS, libgsm_ms); REGISTER_ENCDEC (LIBILBC, libilbc); REGISTER_ENCODER(LIBMP3LAME, libmp3lame); + REGISTER_ENCODER(LIBNVENC, libnvenc); REGISTER_ENCDEC (LIBOPENCORE_AMRNB, libopencore_amrnb); REGISTER_DECODER(LIBOPENCORE_AMRWB, libopencore_amrwb); REGISTER_ENCDEC (LIBOPENJPEG, libopenjpeg); diff --git a/libavcodec/libnvenc.c b/libavcodec/libnvenc.c new file mode 100644 index 0000000..3fc84fd --- /dev/null +++ b/libavcodec/libnvenc.c @@ -0,0 +1,391 @@ +#include "libavutil/opt.h" +#include "avcodec.h" +#include "internal.h" +#include "nvenc.h" +#include + +// ffmpeg-x264 param-to-string macros +#define OPT_STRSTR(x, y)\ + if (y)\ + {\ + x264_argv[x264_argc++] = av_strdup(x); \ + x264_argv[x264_argc++] = av_strdup(y); \ + } +#define OPT_NUMSTR(x, y)\ + if (y > 0)\ + {\ + x264_argv[x264_argc++] = av_strdup(x); \ + x264_argv[x264_argc] = av_malloc(sizeof(char) * 32); \ + sprintf(x264_argv[x264_argc++], "%u", y); \ + } +#define OPT_BOOLSTR(x, y)\ + if (y)\ + {\ + x264_argv[x264_argc++] = av_strdup(x); \ + x264_argv[x264_argc++] = av_strdup("1");\ + } + +typedef struct NvEncContext { + const AVClass *class; + + nvenc_t *nvenc; // NVENC encoder instance + nvenc_cfg_t nvenc_cfg; // NVENC encoder config + + char *x264_opts; // List of x264 options in opt:arg or opt=arg format + char *x264_params; // List of x264 options in opt:arg or opt=arg format + + char *preset; + char *tune; + char *profile; + char *level; + int fastfirstpass; + char *wpredp; + float crf; + float crf_max; + int cqp; + int aq_mode; + float aq_strength; + char *psy_rd; + int psy; + int rc_lookahead; + int weightp; + int weightb; + int ssim; + int intra_refresh; + int bluray_compat; + int b_bias; + int b_pyramid; + int mixed_refs; + int dct8x8; + int fast_pskip; + int aud; + int mbtree; + char *deblock; + float cplxblur; + char *partitions; + int direct_pred; + int slice_max_size; + char *stats; + int nal_hrd; +} NvEncContext; + +static const enum AVPixelFormat nvenc_pix_fmts[] = { + AV_PIX_FMT_NV12, + AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUVJ420P, + AV_PIX_FMT_NONE +}; +static int map_avpixfmt_bufferformat(enum AVPixelFormat pix_fmt) +{ + switch (pix_fmt) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUVJ420P: + return NVENC_FMT_YV12; + case AV_PIX_FMT_NV12: + return NVENC_FMT_NV12; + default: + return 0; + } +} +static int map_avpixfmt_numplanes(enum AVPixelFormat pix_fmt) +{ + switch (pix_fmt) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUVJ420P: + return 3; + case AV_PIX_FMT_NV12: + return 2; + default: + return 3; + } +} + +static av_cold int ff_libnvenc_init(AVCodecContext *avctx) +{ + NvEncContext *nvenc_ctx = (NvEncContext*)avctx->priv_data; + int x264_argc; + char **x264_argv; + + // Basic + nvenc_ctx->nvenc_cfg.width = avctx->width; + nvenc_ctx->nvenc_cfg.height = avctx->height; + nvenc_ctx->nvenc_cfg.frameRateNum = avctx->time_base.den; + nvenc_ctx->nvenc_cfg.frameRateDen = avctx->time_base.num * avctx->ticks_per_frame; + + // Codec + if (avctx->profile >= 0) + nvenc_ctx->nvenc_cfg.profile = avctx->profile; + if (avctx->gop_size >= 0) + nvenc_ctx->nvenc_cfg.gopLength = avctx->gop_size; + else if (!(avctx->flags & CODEC_FLAG_CLOSED_GOP)) + nvenc_ctx->nvenc_cfg.gopLength = UINT_MAX; // infinite GOP + if (avctx->max_b_frames >= 0) + nvenc_ctx->nvenc_cfg.numBFrames = avctx->max_b_frames; + if (avctx->refs >= 0) + nvenc_ctx->nvenc_cfg.numRefFrames = avctx->refs; + if (avctx->flags & CODEC_FLAG_INTERLACED_DCT) + nvenc_ctx->nvenc_cfg.fieldMode = 2; + + // Rate-control + if (avctx->bit_rate > 0) { + nvenc_ctx->nvenc_cfg.rateControl = 2; + nvenc_ctx->nvenc_cfg.avgBitRate = avctx->bit_rate; + } + if (avctx->rc_max_rate >= 0) { + nvenc_ctx->nvenc_cfg.rateControl = 1; + nvenc_ctx->nvenc_cfg.peakBitRate = avctx->rc_max_rate; + } + if (avctx->qmin >= 0) + nvenc_ctx->nvenc_cfg.qpMin = avctx->qmin; + if (avctx->qmax >= 0) + nvenc_ctx->nvenc_cfg.qpMax = avctx->qmax; + if (avctx->rc_buffer_size > 0) { + nvenc_ctx->nvenc_cfg.vbvBufferSize = avctx->rc_buffer_size; + if (avctx->rc_initial_buffer_occupancy >= 0) { + nvenc_ctx->nvenc_cfg.vbvInitialDelay = + avctx->rc_initial_buffer_occupancy / avctx->rc_buffer_size; + } + } + + // Codec-specific + if (avctx->level >= 0) + nvenc_ctx->nvenc_cfg.level = avctx->level; + if (avctx->gop_size >= 0) + nvenc_ctx->nvenc_cfg.idrPeriod = avctx->gop_size; + if (avctx->slices > 0) { + nvenc_ctx->nvenc_cfg.sliceMode = 3; + nvenc_ctx->nvenc_cfg.sliceModeData = avctx->slices; + } + else if (avctx->rtp_payload_size > 0) { + nvenc_ctx->nvenc_cfg.sliceMode = 1; + nvenc_ctx->nvenc_cfg.sliceModeData = avctx->rtp_payload_size; + } + if (avctx->coder_type == FF_CODER_TYPE_AC) + nvenc_ctx->nvenc_cfg.enableCABAC = 1; + if (avctx->flags & CODEC_FLAG_GLOBAL_HEADER) + nvenc_ctx->nvenc_cfg.enableRepeatSPSPPS = 1; + + // Allocate list of x264 options + x264_argc = 0; + x264_argv = av_calloc(255, sizeof(char*)); + if (!x264_argv) + return -1; + + // ffmpeg-x264 parameters + OPT_STRSTR("preset", nvenc_ctx->preset); + OPT_STRSTR("tune", nvenc_ctx->tune); + OPT_STRSTR("profile", nvenc_ctx->profile); + OPT_STRSTR("level", nvenc_ctx->level); + OPT_NUMSTR("qp", nvenc_ctx->cqp); + OPT_NUMSTR("intra-refresh", nvenc_ctx->intra_refresh); + OPT_NUMSTR("aud", nvenc_ctx->aud); + OPT_STRSTR("deblock", nvenc_ctx->deblock); + OPT_NUMSTR("direct-pred", nvenc_ctx->direct_pred); + OPT_NUMSTR("nal_hrd", nvenc_ctx->nal_hrd); + OPT_NUMSTR("8x8dct", nvenc_ctx->dct8x8); + + // x264-style extra parameters + if (nvenc_ctx->x264_params) { + AVDictionary *param_dict = NULL; + AVDictionaryEntry *param_entry = NULL; + + if (!av_dict_parse_string(¶m_dict, nvenc_ctx->x264_params, "=", ":", 0)) { + while ((param_entry = av_dict_get(param_dict, "", param_entry, AV_DICT_IGNORE_SUFFIX))) { + x264_argv[x264_argc++] = av_strdup(param_entry->key); + x264_argv[x264_argc++] = av_strdup(param_entry->value); + } + av_dict_free(¶m_dict); + } + } + // x264-style extra options + if (nvenc_ctx->x264_opts) { + AVDictionary *param_dict = NULL; + AVDictionaryEntry *param_entry = NULL; + + if (!av_dict_parse_string(¶m_dict, nvenc_ctx->x264_opts, "=", ":", 0)) { + while ((param_entry = av_dict_get(param_dict, "", param_entry, AV_DICT_IGNORE_SUFFIX))) { + x264_argv[x264_argc++] = av_strdup(param_entry->key); + x264_argv[x264_argc++] = av_strdup(param_entry->value); + } + av_dict_free(¶m_dict); + } + } + + // Notify encoder to use the list of x264 options + nvenc_ctx->nvenc_cfg.x264_paramc = x264_argc; + nvenc_ctx->nvenc_cfg.x264_paramv = x264_argv; + + // Create and initialize nvencoder + nvenc_ctx->nvenc = nvenc_open(&nvenc_ctx->nvenc_cfg); + if (!nvenc_ctx->nvenc) + return -1; + + avctx->coded_frame = av_frame_alloc(); + if (!avctx->coded_frame) + return AVERROR(ENOMEM); + + avctx->has_b_frames = (nvenc_ctx->nvenc_cfg.numBFrames > 0) ? 1 : 0; + if (avctx->max_b_frames < 0) + avctx->max_b_frames = 0; + avctx->bit_rate = nvenc_ctx->nvenc_cfg.avgBitRate; + + return 0; +} + +static int ff_libnvenc_encode(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet) +{ + NvEncContext *nvenc_ctx = (NvEncContext*)avctx->priv_data; + + int ret, i, user_packet = !!pkt->data; + nvenc_frame_t nvenc_frame; + nvenc_bitstream_t nvenc_bitstream; + + // Check for sudden change in parameters + if ((avctx->width != nvenc_ctx->nvenc_cfg.width) || + (avctx->height != nvenc_ctx->nvenc_cfg.height)) { + ret = nvenc_reconfig(nvenc_ctx->nvenc, &nvenc_ctx->nvenc_cfg); + if (ret < 0) + return -1; + } + + // Setup input + memset(&nvenc_frame, 0, sizeof(nvenc_frame)); + if (frame) { + for (i = 0; i < map_avpixfmt_numplanes(avctx->pix_fmt); i++) { + nvenc_frame.planes[i] = frame->data[i]; + nvenc_frame.stride[i] = frame->linesize[i]; + } + nvenc_frame.width = avctx->width; + nvenc_frame.height = avctx->height; + nvenc_frame.format = map_avpixfmt_bufferformat(avctx->pix_fmt); + } + + // Setup output + ret = ff_alloc_packet2(avctx, pkt, nvenc_frame.width * nvenc_frame.height); + if (ret < 0) + return -1; + memset(&nvenc_bitstream, 0, sizeof(nvenc_bitstream)); + nvenc_bitstream.payload = pkt->data; + nvenc_bitstream.payload_size = pkt->size; + + // Encode the picture + ret = nvenc_encode(nvenc_ctx->nvenc, &nvenc_frame, &nvenc_bitstream); + + if (ret < 0) { + // Encoding failed + if (!user_packet) + av_free_packet(pkt); + return -1; + } else if (ret > 0) { + // Encoding needs more input to produce output + pkt->size = 0; + *got_packet = 0; + return 0; + } else { + // Encoding succeeded + pkt->size = nvenc_bitstream.payload_size; + pkt->flags |= nvenc_bitstream.pic_type == NVENC_PICTYPE_IDR ? AV_PKT_FLAG_KEY : 0; + + avctx->coded_frame->pict_type = + nvenc_bitstream.pic_type == NVENC_PICTYPE_P ? AV_PICTURE_TYPE_P : + nvenc_bitstream.pic_type == NVENC_PICTYPE_B ? AV_PICTURE_TYPE_B : AV_PICTURE_TYPE_I; + avctx->coded_frame->key_frame = + nvenc_bitstream.pic_type == NVENC_PICTYPE_IDR ? 1 : 0; + + *got_packet = 1; + } + + return 0; +} + +static av_cold int ff_libnvenc_close(AVCodecContext *avctx) +{ + NvEncContext *nvenc_ctx = (NvEncContext*)avctx->priv_data; + int i; + + av_freep(&avctx->extradata); + + for (i = 0; i < nvenc_ctx->nvenc_cfg.x264_paramc; i++) + av_freep(&nvenc_ctx->nvenc_cfg.x264_paramv[i]); + av_freep(&nvenc_ctx->nvenc_cfg.x264_paramv); + + // Destroy nvencoder + if (nvenc_ctx->nvenc) + nvenc_close(nvenc_ctx->nvenc); + + av_frame_free(&avctx->coded_frame); + + return 0; +} + + +// Options +#define OFFSET(x) offsetof(NvEncContext, x) +#define OPTIONS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + { "preset" , "Set x264 encoding preset" , OFFSET(preset) , AV_OPT_TYPE_STRING, { .str = "medium" } , 0, 0, OPTIONS}, + { "tune" , "Set x264 encoding tuning" , OFFSET(tune) , AV_OPT_TYPE_STRING, { 0 } , 0, 0, OPTIONS}, + { "profile" , "Set H.264 profile restrictions.", OFFSET(profile) , AV_OPT_TYPE_STRING, { 0 } , 0, 0, OPTIONS}, + { "fastfirstpass" , "Ignored." , OFFSET(fastfirstpass) , AV_OPT_TYPE_INT , { .i64 = 1 } , 0, 1, OPTIONS}, + { "level" , "Set H.264 level" , OFFSET(level) , AV_OPT_TYPE_STRING, { .str=NULL} , 0, 0, OPTIONS}, + { "passlogfile" , "Ignored." , OFFSET(stats) , AV_OPT_TYPE_STRING, { .str=NULL} , 0, 0, OPTIONS}, + { "wpredp" , "Ignored." , OFFSET(wpredp) , AV_OPT_TYPE_STRING, { .str=NULL} , 0, 0, OPTIONS}, + { "crf" , "Ignored." , OFFSET(crf) , AV_OPT_TYPE_FLOAT , { .dbl = -1 } , -1, FLT_MAX, OPTIONS }, + { "crf_max" , "Ignored." , OFFSET(crf_max) , AV_OPT_TYPE_FLOAT , { .dbl = -1 } , -1, FLT_MAX, OPTIONS }, + { "qp" , "Constant quantization parameter", OFFSET(cqp) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, INT_MAX, OPTIONS }, + { "aq-mode" , "Ignored." , OFFSET(aq_mode) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, INT_MAX, OPTIONS, "aq_mode"}, + { "aq-strength" , "Ignored." , OFFSET(aq_strength) , AV_OPT_TYPE_FLOAT , {.dbl = -1} , -1, FLT_MAX, OPTIONS}, + { "psy" , "Ignored." , OFFSET(psy) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS }, + { "psy-rd" , "Ignored." , OFFSET(psy_rd) , AV_OPT_TYPE_STRING, { 0 } , 0, 0, OPTIONS}, + { "rc-lookahead" , "Ignored." , OFFSET(rc_lookahead) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, INT_MAX, OPTIONS }, + { "weightb" , "Ignored." , OFFSET(weightb) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS }, + { "weightp" , "Ignored." , OFFSET(weightp) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, INT_MAX, OPTIONS, "weightp" }, + { "ssim" , "Ignored." , OFFSET(ssim) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS }, + { "intra-refresh" , "Use Periodic Intra Refresh." , OFFSET(intra_refresh) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS }, + { "bluray-compat" , "Ignored." , OFFSET(bluray_compat) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS }, + { "b-bias" , "Ignored." , OFFSET(b_bias) , AV_OPT_TYPE_INT , { .i64 = INT_MIN} , INT_MIN, INT_MAX, OPTIONS }, + { "b-pyramid" , "Ignored." , OFFSET(b_pyramid) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, INT_MAX, OPTIONS, "b_pyramid" }, + { "mixed-refs" , "Ignored." , OFFSET(mixed_refs) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS }, + { "8x8dct" , "High profile 8x8 transform." , OFFSET(dct8x8) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS}, + { "fast-pskip" , "Ignored." , OFFSET(fast_pskip) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS}, + { "aud" , "Insert access unit delimiters." , OFFSET(aud) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS}, + { "mbtree" , "Ignored." , OFFSET(mbtree) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, 1, OPTIONS}, + { "deblock" , "Loop filter, in ." , OFFSET(deblock) , AV_OPT_TYPE_STRING, { 0 }, 0, 0 , OPTIONS}, + { "cplxblur" , "Ignored." , OFFSET(cplxblur) , AV_OPT_TYPE_FLOAT , { .dbl = -1 } , -1, FLT_MAX, OPTIONS}, + { "partitions" , "Ignored." , OFFSET(partitions) , AV_OPT_TYPE_STRING, { 0 }, 0, 0 , OPTIONS}, + { "direct-pred" , "Direct MV prediction mode" , OFFSET(direct_pred) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, INT_MAX, OPTIONS, "direct-pred" }, + { "slice-max-size" , "Ignored." , OFFSET(slice_max_size) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, INT_MAX, OPTIONS }, + { "stats" , "Ignored." , OFFSET(stats) , AV_OPT_TYPE_STRING, { 0 } , 0, 0, OPTIONS }, + { "nal-hrd" , "Insert HRD info NALUs" , OFFSET(nal_hrd) , AV_OPT_TYPE_INT , { .i64 = -1 } , -1, INT_MAX, OPTIONS, "nal-hrd" }, + { "x264opts" , "Apply x264-style options using a :-separated list of key=value pairs", OFFSET(x264_opts) , AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, OPTIONS}, + { "x264-params" , "Apply x264-style options using a :-separated list of key=value pairs", OFFSET(x264_params) , AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, OPTIONS }, + { NULL } , +}; +static const AVCodecDefault nvenc_defaults[] = { + { "qmin" , "-1" }, + { "qmax" , "-1" }, + { "refs" , "-1" }, + { "flags", "+cgop" }, + { NULL }, +}; +static const AVClass nvenc_class = { + .class_name = "NVIDIA NVENC", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVCodec ff_libnvenc_encoder = { + .name = "libnvenc", + .long_name = NULL_IF_CONFIG_SMALL("libnvenc H.264 / MPEG-4 AVC"), + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_H264, + .priv_data_size = sizeof(NvEncContext), + .capabilities = 0, + .pix_fmts = nvenc_pix_fmts, + .priv_class = &nvenc_class, + .defaults = nvenc_defaults, + .init = ff_libnvenc_init, + .encode2 = ff_libnvenc_encode, + .close = ff_libnvenc_close, +}; diff --git a/libavcodec/nvenc.h b/libavcodec/nvenc.h new file mode 100644 index 0000000..ee8ded4 --- /dev/null +++ b/libavcodec/nvenc.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ +#ifndef _NVENC_H +#define _NVENC_H + +#include +#include +#include + +/** + * List of supported pixel formats + */ +enum nvenc_pixfmt_t +{ + NVENC_FMT_NV12, + NVENC_FMT_YV12, +}; + +enum nvenc_pictype_t +{ + NVENC_PICTYPE_P, + NVENC_PICTYPE_B, + NVENC_PICTYPE_I, + NVENC_PICTYPE_IDR, +}; + +/** + * Handle to an encode session + */ +typedef struct nvenc_t nvenc_t; + +/** + * Initialization parameters for an encode session + */ +typedef struct nvenc_cfg_t +{ + // Basic + uint32_t width; + uint32_t height; + uint32_t frameRateNum; + uint32_t frameRateDen; + + // Codec + uint32_t profile; + uint32_t gopLength; + uint32_t numBFrames; + uint32_t numRefFrames; + uint32_t fieldMode; + + // Rate-control + uint32_t rateControl; + uint32_t avgBitRate; + uint32_t peakBitRate; + uint32_t qpI; + uint32_t qpP; + uint32_t qpB; + uint32_t qpMin; + uint32_t qpMax; + uint32_t vbvBufferSize; + uint32_t vbvInitialDelay; + + // H.264 + uint32_t level; + uint32_t idrPeriod; + bool enableCABAC; + bool disableSPSPPS; + bool enableRepeatSPSPPS; + bool enableFMO; + bool enableAUD; + bool enableSEIBufferPeriod; + bool enableSEIPictureTime; + bool enableSEIUser; + bool enableVUI; + uint32_t intraRefreshCount; + uint32_t intraRefreshPeriod; + uint32_t bdirectMode; + uint32_t adaptiveTransformMode; + uint32_t sliceMode; + uint32_t sliceModeData; + uint32_t disableDeblockingFilterIDC; + + // x264-style list of options + char **x264_paramv; + uint32_t x264_paramc; +} nvenc_cfg_t; + +/** + * Configuration parameters for encoding a frame + */ +typedef struct nvenc_frame_t +{ + uint8_t *planes[3]; + uint32_t stride[3]; + + uint32_t width; + uint32_t height; + enum nvenc_pixfmt_t format; + uint32_t frame_idx; + uint32_t frame_type; + bool force_idr; + bool force_intra; +} nvenc_frame_t; + +/** + * Encoded output bitstream data + */ +typedef struct nvenc_bitstream_t +{ + uint8_t *payload; + size_t payload_size; + + uint32_t pic_idx; + enum nvenc_pictype_t pic_type; +} nvenc_bitstream_t; + +/** + * Bitstream header data + */ +typedef struct nvenc_header_t +{ + uint8_t *payload; + size_t payload_size; +} nvenc_header_t; + +nvenc_t* nvenc_open(nvenc_cfg_t *nvenc_cfg); +int nvenc_encode(nvenc_t *nvenc, nvenc_frame_t *nvenc_frame, nvenc_bitstream_t *nvenc_bitstream); +void nvenc_close(nvenc_t *nvenc); +int nvenc_reconfig(nvenc_t *nvenc, nvenc_cfg_t *nvenc_cfg); +int nvenc_header(nvenc_t *nvenc, nvenc_header_t *nvenc_header); + +#endif // _NVENC_H diff --git a/libavcodec/nvencoder.c b/libavcodec/nvencoder.c new file mode 100644 index 0000000..c92e3d5 --- /dev/null +++ b/libavcodec/nvencoder.c @@ -0,0 +1,847 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ +#include "nvenc.h" +#include "nvencoder.h" +#include "nvencoder_utils.h" +#include + +// Definitions +#if defined (_WIN32) +#define NVCUDA_LIB TEXT("nvcuda.dll") +#if defined (_WIN64) +#define NVENCODEAPI_LIB TEXT("nvEncodeAPI64.dll") +#else +#define NVENCODEAPI_LIB TEXT("nvEncodeAPI.dll") +#endif +#else +#include // for memset +#include +#define NVCUDA_LIB "libcuda.so" +#define NVENCODEAPI_LIB "libnvidia-encode.so" +#define LoadLibrary(x) dlopen(x, RTLD_NOW | RTLD_GLOBAL) +#define GetProcAddress dlsym +#define FreeLibrary dlclose +#endif + +// Typedefs +#if defined (NV_CUDACTX) +typedef CUresult (CUDAAPI *cuinit_t)(unsigned int); +typedef CUresult (CUDAAPI *cudeviceget_t)(CUdevice*, int); +typedef CUresult (CUDAAPI *cudevicecomputecapability_t)(int*, int*, CUdevice); +typedef CUresult (CUDAAPI *cuctxcreate_t)(CUcontext*, unsigned int, CUdevice); +typedef CUresult (CUDAAPI *cuctxdestroy_t)(CUcontext); +#endif +#if defined (NV_D3DCTX) +typedef IDirect3D9* (WINAPI *directd3dcreate9_t)(UINT); +#endif +typedef NVENCSTATUS (NVENCAPI *nvencodeapicreateinstance_t)(NV_ENCODE_API_FUNCTION_LIST*); + +// Static data +static const GUID NV_ENC_CODEC_NULL_GUID = +{ 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; + +// Map numeric parameters to NVENCODEAPI definitions +static GUID map_profile(uint32_t profile) +{ + switch (profile) + { + case 66: + return NV_ENC_H264_PROFILE_BASELINE_GUID; + case 77: + return NV_ENC_H264_PROFILE_MAIN_GUID; + case 100: + return NV_ENC_H264_PROFILE_HIGH_GUID; + default: + return NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID; + } +} +static NV_ENC_LEVEL map_level(uint32_t level) +{ + return (NV_ENC_LEVEL)(level); +} + + +static void deinit_device(nvencoder_t *nvenc) +{ +#if defined (NV_CUDACTX) + cuctxdestroy_t cuctxdestroy; + + if (nvenc->device.type == NV_ENC_DEVICE_TYPE_CUDA) + { + if (nvenc->device.cudacontext) + { + cuctxdestroy = + (cuctxdestroy_t)GetProcAddress(nvenc->device.lib, "cuCtxDestroy"); + if (cuctxdestroy) + cuctxdestroy((CUcontext)nvenc->device.cudacontext); + nvenc->device.cudacontext = NULL; + } + if (nvenc->device.cudadevice) + { + nvenc->device.cudadevice = 0; + } + if (nvenc->device.lib) + { + FreeLibrary(nvenc->device.lib); + nvenc->device.lib = NULL; + } + } +#endif // NV_CUDACTX +#if defined (NV_D3DCTX) + if (nvenc->device.type == NV_ENC_DEVICE_TYPE_DIRECTX) + { + if (nvenc->device.d3ddevice) + { + IDirect3DDevice9_Release((IDirect3DDevice9*)nvenc->device.d3ddevice); + nvenc->device.d3ddevice = NULL; + } + if (nvenc->device.d3d) + { + IDirect3D9_Release(nvenc->device.d3d); + nvenc->device.d3d = NULL; + } + if (nvenc->device.lib) + { + FreeLibrary(nvenc->device.lib); + nvenc->device.lib = NULL; + } + } +#endif // NV_D3DCTX + + nvenc->device.ptr = NULL; + nvenc->device.type = 0; +} + +static bool init_device(nvencoder_t *nvenc) +{ +#if defined (NV_CUDACTX) + CUresult cures; + cuinit_t cunit; + cudevicecomputecapability_t cudevicecomputecapability; + cudeviceget_t cudeviceget; + cuctxcreate_t cuctxcreate; + int32_t sm_major, sm_minor; +#endif +#if defined (NV_D3DCTX) + HRESULT hr; + directd3dcreate9_t d3dcreate9; + D3DPRESENT_PARAMETERS d3dpp; +#endif + +#if defined (NV_CUDACTX) + // Allocate CUDA context as basis + nvenc->device.type = NV_ENC_DEVICE_TYPE_CUDA; + nvenc->device.lib = LoadLibrary(NVCUDA_LIB); + if (nvenc->device.lib) + { + cunit = + (cuinit_t)GetProcAddress(nvenc->device.lib, "cuInit"); + cudevicecomputecapability = + (cudevicecomputecapability_t)GetProcAddress(nvenc->device.lib, "cuDeviceComputeCapability"); + cudeviceget = + (cudeviceget_t)GetProcAddress(nvenc->device.lib, "cuDeviceGet"); + cuctxcreate = + (cuctxcreate_t)GetProcAddress(nvenc->device.lib, "cuCtxCreate"); + if (cunit && cudevicecomputecapability && cudeviceget && cuctxcreate) + { + cures = cunit(0); + if (cures == CUDA_SUCCESS) + { + // Get a compatible, NVENC-present CUDA device + cures = cudevicecomputecapability(&sm_major, &sm_minor, 0); + if ((cures == CUDA_SUCCESS) && (sm_major >= 3)) + { + cures = cudeviceget(&nvenc->device.cudadevice, 0); + if (cures == CUDA_SUCCESS) + { + // Create the CUDA Context + cures = cuctxcreate(&nvenc->device.cudacontext, 0, nvenc->device.cudadevice); + if (cures == CUDA_SUCCESS) + { + nvenc->device.ptr = (void*)nvenc->device.cudacontext; + return true; + } + } + } + } + } + } +#endif // NV_CUDACTX + + deinit_device(nvenc); + +#if defined (NV_D3DCTX) + // Allocate D3D context as basis + nvenc->device.type = NV_ENC_DEVICE_TYPE_DIRECTX; + nvenc->device.lib = LoadLibrary(TEXT("d3d9.dll")); + if (nvenc->device.lib) + { + d3dcreate9 = + (directd3dcreate9_t)GetProcAddress(nvenc->device.lib, "Direct3DCreate9"); + if (d3dcreate9) + { + nvenc->device.d3d = d3dcreate9(D3D_SDK_VERSION); + if (nvenc->device.d3d) + { + // Create the Direct3D9 device and the swap chain. In this example, the swap + // chain is the same size as the current display mode. The format is RGB-32. + memset(&d3dpp, 0, sizeof(d3dpp)); + d3dpp.Windowed = TRUE; + d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; + d3dpp.BackBufferWidth = 128; + d3dpp.BackBufferHeight = 128; + d3dpp.BackBufferCount = 0; + d3dpp.SwapEffect = D3DSWAPEFFECT_COPY; + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + d3dpp.Flags = D3DPRESENTFLAG_VIDEO; + + hr = IDirect3D9_CreateDevice( + nvenc->device.d3d, + D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + NULL, + D3DCREATE_MULTITHREADED | D3DCREATE_HARDWARE_VERTEXPROCESSING, + &d3dpp, + (IDirect3DDevice9**)&nvenc->device.d3ddevice); + if (SUCCEEDED(hr)) + { + nvenc->device.ptr = (void*)nvenc->device.d3ddevice; + return true; + } + } + } + } +#endif // NV_D3DCTX + + return false; +} + +static bool query_caps(nvencoder_t *nvenc) +{ + NVENCSTATUS nvenc_status = NV_ENC_ERR_GENERIC; + uint32_t count; + uint32_t count_ret; + + // Enumerate codec GUIDs + count = 0, count_ret = 0; + nvenc_status = nvenc->api.nvEncGetEncodeGUIDCount(nvenc->inst, &count); + if (nvenc_status == NV_ENC_SUCCESS) + { + nvenc->codec_guids = (GUID *)malloc(sizeof(GUID)* count); + if (nvenc->codec_guids) + { + memset(nvenc->codec_guids, 0, sizeof(GUID)* count); + nvenc_status = nvenc->api.nvEncGetEncodeGUIDs(nvenc->inst, nvenc->codec_guids, count, &count_ret); + if ((nvenc_status != NV_ENC_SUCCESS) || (count_ret == 0)) + { + return false; + } + nvenc->num_codec_guids = count_ret; + } + } + + // Enumerate codec profile GUIDs + count = 0, count_ret = 0; + nvenc_status = nvenc->api.nvEncGetEncodeProfileGUIDCount(nvenc->inst, NV_ENC_CODEC_H264_GUID, &count); + if (nvenc_status == NV_ENC_SUCCESS) + { + nvenc->profile_guids = (GUID *)malloc(sizeof(GUID)* count);; + if (nvenc->profile_guids) + { + memset(nvenc->profile_guids, 0, sizeof(GUID)* count); + nvenc_status = nvenc->api.nvEncGetEncodeProfileGUIDs(nvenc->inst, NV_ENC_CODEC_H264_GUID, nvenc->profile_guids, count, &count_ret); + if ((nvenc_status != NV_ENC_SUCCESS) || (count_ret == 0)) + { + return false; + } + nvenc->num_preset_guids = count_ret; + } + } + + // Enumerate codec preset GUIDs + count = 0, count_ret = 0; + nvenc_status = nvenc->api.nvEncGetEncodePresetCount(nvenc->inst, NV_ENC_CODEC_H264_GUID, &count); + if (nvenc_status == NV_ENC_SUCCESS) + { + nvenc->preset_guids = (GUID *)malloc(sizeof(GUID)* count); + if (nvenc->preset_guids) + { + memset(nvenc->preset_guids, 0, sizeof(GUID)* count); + nvenc_status = nvenc->api.nvEncGetEncodePresetGUIDs(nvenc->inst, NV_ENC_CODEC_H264_GUID, nvenc->preset_guids, count, &count_ret); + if ((nvenc_status != NV_ENC_SUCCESS) || (count_ret == 0)) + { + return false; + } + nvenc->num_profile_guids = count_ret; + } + } + + // Enumerate input formats + count = 0, count_ret = 0; + nvenc_status = nvenc->api.nvEncGetInputFormatCount(nvenc->inst, NV_ENC_CODEC_H264_GUID, &count); + if (nvenc_status == NV_ENC_SUCCESS) + { + nvenc->buffer_fmts = (NV_ENC_BUFFER_FORMAT*)malloc(sizeof(NV_ENC_BUFFER_FORMAT)* count); + if (nvenc->buffer_fmts) + { + memset(nvenc->buffer_fmts, 0, sizeof(NV_ENC_BUFFER_FORMAT)* count); + nvenc_status = nvenc->api.nvEncGetInputFormats(nvenc->inst, NV_ENC_CODEC_H264_GUID, nvenc->buffer_fmts, count, &count_ret); + if ((nvenc_status != NV_ENC_SUCCESS) || (count_ret == 0)) + { + return false; + } + nvenc->num_buffer_fmts = count_ret; + } + } + + return true; +} + +static bool open(nvencoder_t *nvenc) +{ + NVENCSTATUS nvenc_status = NV_ENC_ERR_GENERIC; + nvencodeapicreateinstance_t encodeapicreateinst; + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS open_encode_session_params; + + // Dynamically load NVENC library + nvenc->lib = LoadLibrary(NVENCODEAPI_LIB); + + if (!nvenc->lib) + { + return false; + } + encodeapicreateinst = + (nvencodeapicreateinstance_t)GetProcAddress(nvenc->lib, "NvEncodeAPICreateInstance"); + if (!encodeapicreateinst) + { + return false; + } + + // Initialize function table + nvenc->api.version = NV_ENCODE_API_FUNCTION_LIST_VER; + nvenc_status = encodeapicreateinst(&nvenc->api); + if (nvenc_status != NV_ENC_SUCCESS) + { + return false; + } + + if (!init_device(nvenc)) + { + return false; + } + + // Open encoder session + memset(&open_encode_session_params, 0, sizeof(open_encode_session_params)); + open_encode_session_params.version = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER; + open_encode_session_params.apiVersion = NVENCAPI_VERSION; + open_encode_session_params.device = nvenc->device.ptr; + open_encode_session_params.deviceType = nvenc->device.type; + + nvenc_status = nvenc->api.nvEncOpenEncodeSessionEx(&open_encode_session_params, &nvenc->inst); + if (nvenc_status != NV_ENC_SUCCESS) + { + return false; + } + + // Find encoder capabilities + if (!query_caps(nvenc)) + { + return false; + } + + return true; +} + +static bool allocate_io(nvencoder_t *nvenc) +{ + NVENCSTATUS nvenc_status = NV_ENC_ERR_GENERIC; + NV_ENC_CREATE_INPUT_BUFFER create_input_buffer; + NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer; + + // Input buffer + memset(&create_input_buffer, 0, sizeof(create_input_buffer)); + create_input_buffer.version = NV_ENC_CREATE_INPUT_BUFFER_VER; + create_input_buffer.width = nvenc->init_params.maxEncodeWidth; + create_input_buffer.height = nvenc->init_params.maxEncodeHeight; + create_input_buffer.memoryHeap = NV_ENC_MEMORY_HEAP_SYSMEM_UNCACHED; + create_input_buffer.bufferFmt = nvenc->buffer_fmt; + + nvenc_status = nvenc->api.nvEncCreateInputBuffer(nvenc->inst, &create_input_buffer); + if (nvenc_status == NV_ENC_SUCCESS) + { + nvenc->i_buffer = create_input_buffer.inputBuffer; + create_input_buffer.inputBuffer = NULL; + } + + // Output buffer + memset(&create_bitstream_buffer, 0, sizeof(create_bitstream_buffer)); + create_bitstream_buffer.version = NV_ENC_CREATE_BITSTREAM_BUFFER_VER; + create_bitstream_buffer.size = nvenc->init_params.maxEncodeWidth * nvenc->init_params.maxEncodeHeight; + create_bitstream_buffer.memoryHeap = NV_ENC_MEMORY_HEAP_SYSMEM_CACHED; + + nvenc_status = nvenc->api.nvEncCreateBitstreamBuffer(nvenc->inst, &create_bitstream_buffer); + if (nvenc_status == NV_ENC_SUCCESS) + { + nvenc->o_buffer = create_bitstream_buffer.bitstreamBuffer; + create_bitstream_buffer.bitstreamBuffer = NULL; + } + + return true; +} + +static void deallocate_io(nvencoder_t *nvenc) +{ + // Output buffer + if (nvenc->o_buffer) + { + nvenc->api.nvEncDestroyBitstreamBuffer(nvenc->inst, nvenc->o_buffer); + nvenc->o_buffer = NULL; + } + + // Input buffer + if (nvenc->i_buffer) + { + nvenc->api.nvEncDestroyInputBuffer(nvenc->inst, nvenc->i_buffer); + nvenc->i_buffer = NULL; + } +} + +static void close(nvencoder_t *nvenc) +{ + if (nvenc->buffer_fmts) + { + free(nvenc->buffer_fmts); + nvenc->buffer_fmts = NULL; + nvenc->num_buffer_fmts = 0; + } + if (nvenc->preset_guids) + { + free(nvenc->preset_guids); + nvenc->preset_guids = NULL; + nvenc->num_codec_guids = 0; + } + if (nvenc->profile_guids) + { + free(nvenc->profile_guids); + nvenc->profile_guids = NULL; + nvenc->num_profile_guids = 0; + } + if (nvenc->codec_guids) + { + free(nvenc->codec_guids); + nvenc->codec_guids = NULL; + nvenc->num_codec_guids = 0; + } + + if (nvenc->inst) + { + nvenc->api.nvEncDestroyEncoder(nvenc->inst); + nvenc->inst = NULL; + } + + deinit_device(nvenc); + + if (nvenc->lib) + { + FreeLibrary(nvenc->lib); + nvenc->lib = NULL; + } +} + +static bool initialize(nvencoder_t *nvenc, nvenc_cfg_t *nvenc_cfg) +{ + NVENCSTATUS nvenc_status = NV_ENC_ERR_GENERIC; + + //NV_ENC_CONFIG generic + nvenc->config.version = NV_ENC_CONFIG_VER; + nvenc->config.profileGUID = map_profile(nvenc_cfg->profile); + nvenc->config.gopLength = nvenc_cfg->gopLength; + nvenc->config.frameIntervalP = 1 + nvenc_cfg->numBFrames; + nvenc->config.frameFieldMode = NV_ENC_PARAMS_FRAME_FIELD_MODE_FRAME; + nvenc->config.mvPrecision = NV_ENC_MV_PRECISION_QUARTER_PEL; + + //NV_ENC_CODEC_CONFIG rate-control + nvenc->config.rcParams.version = NV_ENC_RC_PARAMS_VER; + nvenc->config.rcParams.rateControlMode = (NV_ENC_PARAMS_RC_MODE)nvenc_cfg->rateControl; + nvenc->config.rcParams.maxBitRate = nvenc_cfg->peakBitRate; + nvenc->config.rcParams.averageBitRate = nvenc_cfg->avgBitRate; + nvenc->config.rcParams.constQP.qpIntra = nvenc_cfg->qpI; + nvenc->config.rcParams.constQP.qpInterP = nvenc_cfg->qpP; + nvenc->config.rcParams.constQP.qpInterB = nvenc_cfg->qpB; + nvenc->config.rcParams.minQP.qpIntra = nvenc_cfg->qpMin; + nvenc->config.rcParams.minQP.qpInterP = nvenc_cfg->qpMin; + nvenc->config.rcParams.minQP.qpInterB = nvenc_cfg->qpMin; + nvenc->config.rcParams.maxQP.qpIntra = nvenc_cfg->qpMax; + nvenc->config.rcParams.maxQP.qpInterP = nvenc_cfg->qpMax; + nvenc->config.rcParams.maxQP.qpInterB = nvenc_cfg->qpMax; + + //NV_ENC_CODEC_CONFIG codec + nvenc->config.encodeCodecConfig.h264Config.outputAUD = nvenc_cfg->enableAUD; + nvenc->config.encodeCodecConfig.h264Config.disableSPSPPS = nvenc_cfg->disableSPSPPS; + nvenc->config.encodeCodecConfig.h264Config.repeatSPSPPS = nvenc_cfg->enableRepeatSPSPPS; + nvenc->config.encodeCodecConfig.h264Config.level = map_level(nvenc_cfg->level); + nvenc->config.encodeCodecConfig.h264Config.idrPeriod = nvenc_cfg->idrPeriod; + nvenc->config.encodeCodecConfig.h264Config.fmoMode = NV_ENC_H264_FMO_DISABLE; + nvenc->config.encodeCodecConfig.h264Config.maxNumRefFrames = nvenc_cfg->numRefFrames; + nvenc->config.encodeCodecConfig.h264Config.chromaFormatIDC = 1; + nvenc->config.encodeCodecConfig.h264Config.bdirectMode = (NV_ENC_H264_BDIRECT_MODE)nvenc_cfg->bdirectMode; + nvenc->config.encodeCodecConfig.h264Config.adaptiveTransformMode = (NV_ENC_H264_ADAPTIVE_TRANSFORM_MODE)nvenc_cfg->adaptiveTransformMode; + nvenc->config.encodeCodecConfig.h264Config.sliceMode = nvenc_cfg->sliceMode; + nvenc->config.encodeCodecConfig.h264Config.sliceModeData = nvenc_cfg->sliceModeData; + nvenc->config.encodeCodecConfig.h264Config.outputBufferingPeriodSEI = nvenc_cfg->enableSEIBufferPeriod; + nvenc->config.encodeCodecConfig.h264Config.outputPictureTimingSEI = nvenc_cfg->enableSEIPictureTime; + nvenc->config.encodeCodecConfig.h264Config.disableDeblockingFilterIDC = nvenc_cfg->disableDeblockingFilterIDC; + + //NV_ENC_INIT_PARAMS + nvenc->init_params.encodeConfig = &nvenc->config; + nvenc->init_params.version = NV_ENC_INITIALIZE_PARAMS_VER; + nvenc->init_params.encodeGUID = NV_ENC_CODEC_H264_GUID; + nvenc->init_params.presetGUID = NV_ENC_PRESET_HQ_GUID; + nvenc->init_params.encodeWidth = nvenc_cfg->width; + nvenc->init_params.encodeHeight = nvenc_cfg->height; + nvenc->init_params.darWidth = nvenc_cfg->width; + nvenc->init_params.darHeight = nvenc_cfg->height; + nvenc->init_params.frameRateNum = nvenc_cfg->frameRateNum; + nvenc->init_params.frameRateDen = nvenc_cfg->frameRateDen; + nvenc->init_params.enableEncodeAsync = 0; + nvenc->init_params.enablePTD = 1; + nvenc->init_params.maxEncodeWidth = nvenc_cfg->width; + nvenc->init_params.maxEncodeHeight = nvenc_cfg->height; + + // Apply x264-style options that will override the above settings + map_x264_params(&nvenc->init_params, nvenc_cfg->x264_paramc, nvenc_cfg->x264_paramv); + + nvenc_status = nvenc->api.nvEncInitializeEncoder(nvenc->inst, &nvenc->init_params); + if (nvenc_status == NV_ENC_SUCCESS) + { + // Default to NV12 as preferred input format + nvenc->buffer_fmt = NV_ENC_BUFFER_FORMAT_NV12_PL; + + return true; + } + + return false; +} + +static bool encode_frame(nvencoder_t *nvenc, nvenc_frame_t *nvenc_frame, bool *output, bool flush) +{ + NVENCSTATUS nvenc_status = NV_ENC_ERR_GENERIC; + NV_ENC_PIC_PARAMS pic_params; + + memset(&pic_params, 0, sizeof(pic_params)); + if (flush) + { + pic_params.version = NV_ENC_PIC_PARAMS_VER; + pic_params.encodePicFlags |= NV_ENC_PIC_FLAG_EOS; + } + else + { + pic_params.version = NV_ENC_PIC_PARAMS_VER; + pic_params.inputWidth = nvenc_frame->width; + pic_params.inputHeight = nvenc_frame->height; + pic_params.inputBuffer = nvenc->i_buffer; + pic_params.outputBitstream = nvenc->o_buffer; + pic_params.bufferFmt = nvenc->buffer_fmt; + pic_params.pictureStruct = NV_ENC_PIC_STRUCT_FRAME; + pic_params.frameIdx = nvenc_frame->frame_idx; + if (nvenc_frame->force_idr) + pic_params.encodePicFlags |= NV_ENC_PIC_FLAG_FORCEIDR; + if (nvenc_frame->force_intra) + pic_params.encodePicFlags |= NV_ENC_PIC_FLAG_FORCEINTRA; + } + + nvenc_status = nvenc->api.nvEncEncodePicture(nvenc->inst, &pic_params); + if (nvenc_status == NV_ENC_SUCCESS) + { + *output = true; + return true; + } + if (nvenc_status == NV_ENC_ERR_NEED_MORE_INPUT) + { + *output = false; + return true; + } + + return false; +} + +static bool feed_input(nvencoder_t *nvenc, uint8_t **planes, uint32_t *pitches, enum nvenc_pixfmt_t buffer_fmt) +{ + NVENCSTATUS nvenc_status = NV_ENC_ERR_GENERIC; + NV_ENC_LOCK_INPUT_BUFFER lock_input_buffer; + uint8_t *src, *dst; + uint32_t src_pitch, dst_pitch, x, y; + + memset(&lock_input_buffer, 0, sizeof(lock_input_buffer)); + lock_input_buffer.version = NV_ENC_LOCK_BITSTREAM_VER; + lock_input_buffer.inputBuffer = nvenc->i_buffer; + + nvenc_status = nvenc->api.nvEncLockInputBuffer(nvenc->inst, &lock_input_buffer); + if (nvenc_status == NV_ENC_SUCCESS) + { + dst = (uint8_t*)lock_input_buffer.bufferDataPtr; + dst_pitch = lock_input_buffer.pitch; + + if (buffer_fmt == NVENC_FMT_NV12) + { + // Y + src = planes[0]; + src_pitch = pitches[0]; + for (y = 0; y < nvenc->init_params.encodeHeight; y++) + { + memcpy(dst, src, nvenc->init_params.encodeWidth); + dst += dst_pitch; + src += src_pitch; + } + // UV + src = planes[1]; + src_pitch = pitches[1]; + for (y = 0; y < nvenc->init_params.encodeHeight / 2; y++) + { + memcpy(dst, src, nvenc->init_params.encodeWidth); + dst += dst_pitch; + src += src_pitch; + } + } + else if (buffer_fmt == NVENC_FMT_YV12) + { + // Y + src = planes[0]; + src_pitch = pitches[0]; + for (y = 0; y < nvenc->init_params.encodeHeight; y++) + { + memcpy(dst, src, nvenc->init_params.encodeWidth); + dst += dst_pitch; + src += src_pitch; + } + // UV interleaving + for (y = 0; y < nvenc->init_params.encodeHeight / 2; y++) + { + for (x = 0; x < nvenc->init_params.encodeWidth; x += 2) + { + dst[x] = planes[1][(pitches[1] * y) + (x >> 1)]; + dst[x + 1] = planes[2][(pitches[2] * y) + (x >> 1)]; + } + dst += dst_pitch; + } + } + + nvenc->api.nvEncUnlockInputBuffer(nvenc->inst, nvenc->i_buffer); + + return true; + } + + return false; +} + +static bool fetch_output(nvencoder_t *nvenc, nvenc_bitstream_t *nvenc_bitstream) +{ + NVENCSTATUS nvenc_status = NV_ENC_ERR_GENERIC; + NV_ENC_LOCK_BITSTREAM lock_bitstream; + uint8_t *src; + uint32_t src_size; + + memset(&lock_bitstream, 0, sizeof(lock_bitstream)); + lock_bitstream.version = NV_ENC_LOCK_BITSTREAM_VER; + lock_bitstream.doNotWait = 0; + lock_bitstream.outputBitstream = nvenc->o_buffer; + + nvenc_status = nvenc->api.nvEncLockBitstream(nvenc->inst, &lock_bitstream); + if (nvenc_status == NV_ENC_SUCCESS) + { + src = (uint8_t*)lock_bitstream.bitstreamBufferPtr; + src_size = lock_bitstream.bitstreamSizeInBytes; + + // copy bitstream out + if (nvenc_bitstream->payload && + nvenc_bitstream->payload_size >= src_size) + { + memcpy(nvenc_bitstream->payload, src, src_size); + nvenc_bitstream->payload_size = src_size; + nvenc_bitstream->pic_idx = lock_bitstream.frameIdx; + nvenc_bitstream->pic_type = lock_bitstream.pictureType; + } + + nvenc->api.nvEncUnlockBitstream(nvenc->inst, nvenc->o_buffer); + + return true; + } + + return false; +} + +static bool reconfig(nvencoder_t *nvenc, nvenc_cfg_t *nvenc_cfg) +{ + NVENCSTATUS nvenc_status = NV_ENC_ERR_GENERIC; + NV_ENC_RECONFIGURE_PARAMS reconfig_params; + + // Update initial encoder parameters that likely changed + nvenc->init_params.encodeWidth = nvenc_cfg->width; + nvenc->init_params.encodeHeight = nvenc_cfg->height; + nvenc->init_params.darWidth = nvenc_cfg->width; + nvenc->init_params.darWidth = nvenc_cfg->height; + nvenc->init_params.frameRateNum = nvenc_cfg->frameRateNum; + nvenc->init_params.frameRateDen = nvenc_cfg->frameRateDen; + nvenc->init_params.encodeConfig->rcParams.averageBitRate = nvenc_cfg->avgBitRate; + nvenc->init_params.encodeConfig->rcParams.maxBitRate = nvenc_cfg->peakBitRate; + + // Update x264-style options that will override the above settings + map_x264_params(&nvenc->init_params, nvenc_cfg->x264_paramc, nvenc_cfg->x264_paramv); + + memset(&reconfig_params, 0, sizeof(reconfig_params)); + reconfig_params.version = NV_ENC_RECONFIGURE_PARAMS_VER; + reconfig_params.resetEncoder = 1; + memcpy(&reconfig_params.reInitEncodeParams, &nvenc->init_params, sizeof(nvenc->init_params)); + + nvenc_status = nvenc->api.nvEncReconfigureEncoder(nvenc->inst, &reconfig_params); + if (nvenc_status == NV_ENC_SUCCESS) + { + return true; + } + + return false; +} + +static bool get_header(nvencoder_t *nvenc, uint8_t **header, size_t *header_size) +{ + NVENCSTATUS nvenc_status = NV_ENC_ERR_GENERIC; + NV_ENC_SEQUENCE_PARAM_PAYLOAD seq_param_payload; + + nvenc_status = nvenc->api.nvEncGetSequenceParams(nvenc->inst, &seq_param_payload); + if (nvenc_status == NV_ENC_SUCCESS) + { + // copy header out + if ((header && header_size) && + (*header_size >= (size_t)seq_param_payload.outSPSPPSPayloadSize)) + { + *header_size = (size_t)seq_param_payload.outSPSPPSPayloadSize; + memcpy(*header, seq_param_payload.spsppsBuffer, *header_size); + return true; + } + } + + return false; +} + + +/** + * Creates and initializes a new encode session. + * + * @param nvenc_cfg The encoder initialization parameters + * @return The encoder instance, NULL on failure + */ +nvenc_t* nvenc_open(nvenc_cfg_t *nvenc_cfg) +{ + nvencoder_t *_nvenc = (nvencoder_t *)malloc(sizeof(nvencoder_t)); + if (_nvenc) + { + memset(_nvenc, 0, sizeof(nvencoder_t)); + + if (open(_nvenc) && + initialize(_nvenc, nvenc_cfg) && + allocate_io(_nvenc)) + { + return (nvenc_t*)_nvenc; + } + deallocate_io(_nvenc); + close(_nvenc); + free(_nvenc); + } + return NULL; +} + +/** + * Re-initializes an existing encode session with new parameters. + * + * Only a subset of encoding parameters can be changed, which includes, not are + * not necessarily limited to: dimensions, framerate, and bitrate. + * + * @param nvenc The encoder instance + * @param nvenc_cfg The encoder initialization parameters + * @return 0 on success, negative on failure + */ +int nvenc_reconfig(nvenc_t *nvenc, nvenc_cfg_t *nvenc_cfg) +{ + nvencoder_t *_nvenc = (nvencoder_t*)nvenc; + if (_nvenc) + { + if (reconfig(_nvenc, nvenc_cfg)) + { + return 0; + } + } + return -1; +} + +/** + * Encodes a single picture with the given encode input and encode parameters + * + * @param nvenc The encoder instance + * @param nvenc_frame The input data and config for the current picture + * @param nvenc_bitstream The encoded output data + * @return 0 on success, negative on failure, 1 on require more input + */ +int nvenc_encode(nvenc_t *nvenc, nvenc_frame_t *nvenc_frame, nvenc_bitstream_t *nvenc_bitstream) +{ + bool output; + nvencoder_t *_nvenc = (nvencoder_t*)nvenc; + if (_nvenc) + { + // Simple synchronous encoding + if (feed_input(_nvenc, nvenc_frame->planes, nvenc_frame->stride, nvenc_frame->format) && + encode_frame(_nvenc, nvenc_frame, &output, false)) + { + if (!output) + { + return 1; + } + if (fetch_output(_nvenc, nvenc_bitstream)) + { + return 0; + } + } + } + + return -1; +} + +/** +* Retrieves the codec bitstream headers for the encode session. +* +* @param nvenc The encoder instance +* @param nvenc_header The buffer to hold the bitstream header +* @return 0 on success, negative on failure +*/ +int nvenc_header(nvenc_t *nvenc, nvenc_header_t *nvenc_header) +{ + nvencoder_t *_nvenc = (nvencoder_t*)nvenc; + if (_nvenc) + { + if (get_header(_nvenc, &nvenc_header->payload, &nvenc_header->payload_size)) + { + return 0; + } + } + return -1; +} + +/** +* Closes the encode session and releases its resources. +* +* @param nvenc The encoder instance +*/ +void nvenc_close(nvenc_t *nvenc) +{ + bool output; + nvencoder_t *_nvenc = (nvencoder_t*)nvenc; + if (_nvenc) + { + // Flush encoder + encode_frame(_nvenc, NULL, &output, true); + + deallocate_io(_nvenc); + close(_nvenc); + free(_nvenc); + } +} diff --git a/libavcodec/nvencoder.h b/libavcodec/nvencoder.h new file mode 100644 index 0000000..f69e397 --- /dev/null +++ b/libavcodec/nvencoder.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ +#ifndef _NVENCODER_H +#define _NVENCODER_H + +#include "nvEncodeAPI.h" +#include +#include + +#if !defined (_WIN32) +#define NVENCAPI +typedef void* HINSTANCE; +typedef void* HANDLE; +#endif + +// Allow either CUDA/D3D or both as context to encoding interface +#define NV_CUDACTX +#if defined (_WIN32) +#define NV_D3DCTX +#endif + +#if defined (NV_D3DCTX) +#include +#endif +#if defined (NV_CUDACTX) +#include "cuda.h" +#endif + +// Main encoding context +typedef struct nvencoder_t +{ + HINSTANCE lib; + NV_ENCODE_API_FUNCTION_LIST api; + + HANDLE inst; + GUID *codec_guids; + GUID *profile_guids; + GUID *preset_guids; + NV_ENC_BUFFER_FORMAT *buffer_fmts; + uint32_t num_codec_guids; + uint32_t num_profile_guids; + uint32_t num_preset_guids; + uint32_t num_buffer_fmts; + + NV_ENC_INITIALIZE_PARAMS init_params; + NV_ENC_CONFIG config; + NV_ENC_BUFFER_FORMAT buffer_fmt; + + NV_ENC_INPUT_PTR i_buffer; + NV_ENC_OUTPUT_PTR o_buffer; + + struct + { + HINSTANCE lib; +#if defined (NV_D3DCTX) + IDirect3D9 *d3d; + IDirect3DDevice9 *d3ddevice; +#endif +#if defined (NV_CUDACTX) + CUdevice cudadevice; + CUcontext cudacontext; +#endif + void *ptr; + NV_ENC_DEVICE_TYPE type; + } device; +} nvencoder_t; + +#endif // _NVENCODER_H diff --git a/libavcodec/nvencoder_utils.c b/libavcodec/nvencoder_utils.c new file mode 100644 index 0000000..cea40f0 --- /dev/null +++ b/libavcodec/nvencoder_utils.c @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ +#include "nvencoder_utils.h" +#include +#include +#include + +// List of all current x264 options +typedef struct x264_params_t +{ + // Presets + char *profile; + char *preset; + char *tune; + + // Frame + uint32_t keyint; + uint32_t min_keyint; + bool intra_refresh; + uint32_t bframes; + bool open_gop; + bool no_cabac; + uint32_t ref; + bool no_deblock; + uint32_t slices; + + // Rate control + uint32_t qp; + uint32_t bitrate; + uint32_t vbv_maxrate; + uint32_t vbv_bufsize; + float vbv_init; + uint32_t qpmin; + uint32_t qpmax; + float ipratio; + float pbratio; + + // Input/Output + char *output; + char *input_fmt; + char *input_csp; + char *output_csp; + uint32_t input_depth; + char *input_range; + char *fps; + uint32_t seek; + uint32_t frames; + float level; + bool aud; +} x264_params_t; + +static bool guidcmp(const GUID *guid1, const GUID *guid2) +{ + return memcmp(guid1, guid2, sizeof(GUID)) == 0 ? true : false; +} + +static GUID map_profile(const char *profile) +{ + if (!strncmp(profile, "baseline", 8)) + return NV_ENC_H264_PROFILE_BASELINE_GUID; + if (!strncmp(profile, "main", 4)) + return NV_ENC_H264_PROFILE_MAIN_GUID; + if (!strncmp(profile, "high", 4)) + return NV_ENC_H264_PROFILE_HIGH_GUID; + + return NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID; +} +static GUID map_preset(const char *profile) +{ + // all the fast presets + if (strstr(profile, "fast")) + return NV_ENC_PRESET_HP_GUID; + + // all the slow presets + if (strstr(profile, "slow")) + return NV_ENC_PRESET_HQ_GUID; + + return NV_ENC_PRESET_LOW_LATENCY_DEFAULT_GUID; +} +static GUID map_tune(const char *tune, const GUID *cur_preset) +{ + // quality + if (!strncmp(tune, "psnr", 4) || !strncmp(tune, "ssim", 4)) + return NV_ENC_PRESET_HQ_GUID; + + // low-latency + if (!strncmp(tune, "zerolatency", 11)) + { + if (guidcmp(cur_preset, &NV_ENC_PRESET_HQ_GUID)) + return NV_ENC_PRESET_LOW_LATENCY_HQ_GUID; + else if (guidcmp(cur_preset, &NV_ENC_PRESET_HP_GUID)) + return NV_ENC_PRESET_LOW_LATENCY_HP_GUID; + else + return NV_ENC_PRESET_LOW_LATENCY_DEFAULT_GUID; + } + + return NV_ENC_PRESET_DEFAULT_GUID; +} + +static bool parse_x264_params(x264_params_t *x264_params, char *x264_opt, char *x264_arg) +{ + if (!strcmp(x264_opt, "profile")) + x264_params->profile = x264_arg; + if (!strcmp(x264_opt, "preset")) + x264_params->preset = x264_arg; + + if (!strcmp(x264_opt, "keyint")) + x264_params->keyint = atoi(x264_arg); + if (!strcmp(x264_opt, "min-keyint")) + x264_params->min_keyint = atoi(x264_arg); + if (!strcmp(x264_opt, "intra-refresh")) + x264_params->intra_refresh = true; + if (!strcmp(x264_opt, "bframes")) + x264_params->bframes = atoi(x264_arg); + if (!strcmp(x264_opt, "open-gop")) + x264_params->open_gop = true; + if (!strcmp(x264_opt, "no-cabac")) + x264_params->no_cabac = true; + if (!strcmp(x264_opt, "ref")) + x264_params->ref = atoi(x264_arg); + if (!strcmp(x264_opt, "no-deblock")) + x264_params->no_deblock = true; + if (!strcmp(x264_opt, "slices")) + x264_params->slices = atoi(x264_arg); + + if (!strcmp(x264_opt, "qp")) + x264_params->qp = atoi(x264_arg); + if (!strcmp(x264_opt, "bitrate")) + x264_params->bitrate = atoi(x264_arg); + if (!strcmp(x264_opt, "vbv-maxrate")) + x264_params->vbv_maxrate = atoi(x264_arg); + if (!strcmp(x264_opt, "vbv-bufsize")) + x264_params->vbv_bufsize = atoi(x264_arg); + if (!strcmp(x264_opt, "vbv-init")) + x264_params->vbv_init = (float)atof(x264_arg); + if (!strcmp(x264_opt, "qpmin")) + x264_params->qpmin = atoi(x264_arg); + if (!strcmp(x264_opt, "qpmax")) + x264_params->qpmax = atoi(x264_arg); + if (!strcmp(x264_opt, "ipratio")) + x264_params->ipratio = (float)atof(x264_arg); + if (!strcmp(x264_opt, "pbratio")) + x264_params->pbratio = (float)atof(x264_arg); + + if (!strcmp(x264_opt, "level")) + x264_params->level = (float)atof(x264_arg); + if (!strcmp(x264_opt, "aud")) + x264_params->aud = true; + + return true; +} + +static bool map_x264_to_nvenc(NV_ENC_INITIALIZE_PARAMS *nvenc_init_params, const x264_params_t *x264_params) +{ + uint32_t qp_ipdiff = (uint32_t)(6 * log2f(x264_params->ipratio > 0.f ? x264_params->ipratio : 1.4f)); + uint32_t qp_pbdiff = (uint32_t)(6 * log2f(x264_params->pbratio > 0.f ? x264_params->pbratio : 1.3f)); + + // Preset + if (x264_params->profile) + { + nvenc_init_params->encodeConfig->profileGUID = map_profile(x264_params->profile); + } + if (x264_params->preset) + { + nvenc_init_params->presetGUID = map_preset(x264_params->preset); + } + if (x264_params->tune) + { + nvenc_init_params->presetGUID = map_tune(x264_params->tune, &nvenc_init_params->presetGUID); + } + + // Frame + if ((x264_params->keyint > 0) || (x264_params->min_keyint > 0)) + { + if ((x264_params->keyint == 0) || (x264_params->keyint > x264_params->min_keyint)) + nvenc_init_params->encodeConfig->gopLength = x264_params->min_keyint; + else + nvenc_init_params->encodeConfig->gopLength = x264_params->keyint; + fprintf(stdout, "%s=%u\n", "gopLength", nvenc_init_params->encodeConfig->gopLength); + } + if (x264_params->intra_refresh) + { + const uint32_t fps = nvenc_init_params->frameRateNum / nvenc_init_params->frameRateDen; + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.enableIntraRefresh = 1; + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.intraRefreshCnt = fps; + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.intraRefreshPeriod = fps; + fprintf(stdout, "%s=%u\n", "enableIntraRefresh", fps); + } + if (x264_params->bframes > 0) + { + nvenc_init_params->encodeConfig->frameIntervalP = 0 + 1 + x264_params->bframes; + fprintf(stdout, "%s=%u\n", "frameIntervalP", + nvenc_init_params->encodeConfig->frameIntervalP); + } + if (x264_params->open_gop) + { + nvenc_init_params->encodeConfig->gopLength = NVENC_INFINITE_GOPLENGTH; + fprintf(stdout, "%s=%s\n", "gopLength", "NVENC_INFINITE_GOPLENGTH"); + } + if (x264_params->no_cabac) + { + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC; + fprintf(stdout, "%s=%s\n", "entropyCodingMode", "NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC"); + } + if (x264_params->ref > 0) + { + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.maxNumRefFrames = x264_params->ref; + fprintf(stdout, "%s=%u\n", "maxNumRefFrames", + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.maxNumRefFrames); + } + if (x264_params->no_deblock > 0) + { + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.disableDeblockingFilterIDC = x264_params->no_deblock; + fprintf(stdout, "%s=%x\n", "disableDeblockingFilterIDC", + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.disableDeblockingFilterIDC); + } + if (x264_params->slices > 0) + { + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.sliceMode = 3; + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.sliceModeData = x264_params->slices; + fprintf(stdout, "%s=%u,%s=%u\n", + "sliceMode", nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.sliceMode, + "sliceModeData", nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.maxNumRefFrames); + } + + // Rate control + if (x264_params->qp > 0) + { + nvenc_init_params->encodeConfig->rcParams.rateControlMode = NV_ENC_PARAMS_RC_CONSTQP; + nvenc_init_params->encodeConfig->rcParams.constQP.qpInterP = x264_params->qp; + nvenc_init_params->encodeConfig->rcParams.constQP.qpInterB = x264_params->qp + qp_pbdiff; + nvenc_init_params->encodeConfig->rcParams.constQP.qpIntra = x264_params->qp - qp_ipdiff; + fprintf(stdout, "%s=%u,%s=%u,%s=%u\n", + "constQP.qpInterP", nvenc_init_params->encodeConfig->rcParams.constQP.qpInterP, + "constQP.qpInterB", nvenc_init_params->encodeConfig->rcParams.constQP.qpInterB, + "constQP.qpIntra" , nvenc_init_params->encodeConfig->rcParams.constQP.qpIntra); + } + if (x264_params->bitrate > 0) + { + nvenc_init_params->encodeConfig->rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR; + nvenc_init_params->encodeConfig->rcParams.averageBitRate = x264_params->bitrate; + fprintf(stdout, "%s=%u\n", "averageBitRate", + nvenc_init_params->encodeConfig->rcParams.averageBitRate); + } + if (x264_params->vbv_maxrate > 0) + { + nvenc_init_params->encodeConfig->rcParams.maxBitRate = x264_params->vbv_maxrate; + fprintf(stdout, "%s=%u\n", "maxBitRate", + nvenc_init_params->encodeConfig->rcParams.maxBitRate); + } + if (x264_params->vbv_bufsize > 0) + { + nvenc_init_params->encodeConfig->rcParams.vbvBufferSize = x264_params->vbv_bufsize; + fprintf(stdout, "%s=%u\n", "vbvBufferSize", + nvenc_init_params->encodeConfig->rcParams.vbvBufferSize); + } + if (x264_params->vbv_init > 0.f) + { + nvenc_init_params->encodeConfig->rcParams.vbvInitialDelay = (uint32_t)x264_params->vbv_init; + fprintf(stdout, "%s=%u\n", "vbvInitialDelay", + nvenc_init_params->encodeConfig->rcParams.vbvInitialDelay); + } + if (x264_params->qpmin > 0.f) + { + nvenc_init_params->encodeConfig->rcParams.enableMinQP = 1; + nvenc_init_params->encodeConfig->rcParams.minQP.qpInterP = x264_params->qpmin; + nvenc_init_params->encodeConfig->rcParams.minQP.qpInterB = x264_params->qpmin; + nvenc_init_params->encodeConfig->rcParams.minQP.qpIntra = x264_params->qpmin; + fprintf(stdout, "%s=%u,%s=%u,%s=%u\n", + "minQP.qpInterP", nvenc_init_params->encodeConfig->rcParams.minQP.qpInterP, + "minQP.qpInterB", nvenc_init_params->encodeConfig->rcParams.minQP.qpInterB, + "minQP.qpIntra" , nvenc_init_params->encodeConfig->rcParams.minQP.qpIntra); + } + if (x264_params->qpmax > 0.f) + { + nvenc_init_params->encodeConfig->rcParams.enableMaxQP = 1; + nvenc_init_params->encodeConfig->rcParams.maxQP.qpInterP = x264_params->qpmax; + nvenc_init_params->encodeConfig->rcParams.maxQP.qpInterB = x264_params->qpmax; + nvenc_init_params->encodeConfig->rcParams.maxQP.qpIntra = x264_params->qpmax; + fprintf(stdout, "%s=%u,%s=%u,%s=%u\n", + "maxQP.qpInterP", nvenc_init_params->encodeConfig->rcParams.maxQP.qpInterP, + "maxQP.qpInterB", nvenc_init_params->encodeConfig->rcParams.maxQP.qpInterB, + "maxQP.qpIntra" , nvenc_init_params->encodeConfig->rcParams.maxQP.qpIntra); + } + + if (x264_params->level > 0.f) + { + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.level = + (uint32_t)(x264_params->level * 10.f); + fprintf(stdout, "%s=%u\n", "level", + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.level); + } + if (x264_params->aud) + { + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.outputAUD = x264_params->aud; + fprintf(stdout, "%s=%u\n", "aud", + nvenc_init_params->encodeConfig->encodeCodecConfig.h264Config.outputAUD); + } + + return false; +} + +bool map_x264_params(NV_ENC_INITIALIZE_PARAMS *nvenc_init_params, uint32_t argc, char *argv[]) +{ + x264_params_t *x264_params; + uint32_t i; + + x264_params = malloc(sizeof(x264_params_t)); + if (x264_params) + { + memset(x264_params, 0, sizeof(x264_params_t)); + + // First, parse all understandable options (that appear in any order) + for (i = 0; i < argc; i += 2) + { + parse_x264_params(x264_params, argv[i], argv[i + 1]); + } + // Second, map and apply the x264 options to nvenc all at once + map_x264_to_nvenc(nvenc_init_params, x264_params); + + free(x264_params); + + return true; + } + + return false; +} diff --git a/libavcodec/nvencoder_utils.h b/libavcodec/nvencoder_utils.h new file mode 100644 index 0000000..b74a279 --- /dev/null +++ b/libavcodec/nvencoder_utils.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ +#ifndef _NVENCODER_UTILS_H +#define _NVENCODER_UTILS_H + +#include "nvEncodeAPI.h" +#include +#include + +bool map_x264_params(NV_ENC_INITIALIZE_PARAMS *nvenc_init_params, uint32_t argc, char *argv[]); + +#endif //_NVENCODER_UTILS_H diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index a0b5950..366fac2 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1995,7 +1995,7 @@ AVOutputFormat ff_matroska_muxer = { .priv_data_size = sizeof(MatroskaMuxContext), .audio_codec = CONFIG_LIBVORBIS_ENCODER ? AV_CODEC_ID_VORBIS : AV_CODEC_ID_AC3, - .video_codec = CONFIG_LIBX264_ENCODER ? + .video_codec = (CONFIG_LIBX264_ENCODER || CONFIG_LIBNVENC_ENCODER) ? AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4, .write_header = mkv_write_header, .write_packet = mkv_write_flush_packet, diff --git a/libavformat/movenc.c b/libavformat/movenc.c index a43752a..7815401 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -4865,7 +4865,7 @@ AVOutputFormat ff_mov_muxer = { .extensions = "mov", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, - .video_codec = CONFIG_LIBX264_ENCODER ? + .video_codec = (CONFIG_LIBX264_ENCODER || CONFIG_LIBNVENC_ENCODER) ? AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4, .write_header = mov_write_header, .write_packet = mov_write_packet, @@ -4903,7 +4903,7 @@ AVOutputFormat ff_mp4_muxer = { .extensions = "mp4", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, - .video_codec = CONFIG_LIBX264_ENCODER ? + .video_codec = (CONFIG_LIBX264_ENCODER || CONFIG_LIBNVENC_ENCODER) ? AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4, .write_header = mov_write_header, .write_packet = mov_write_packet, @@ -4921,7 +4921,7 @@ AVOutputFormat ff_psp_muxer = { .extensions = "mp4,psp", .priv_data_size = sizeof(MOVMuxContext), .audio_codec = AV_CODEC_ID_AAC, - .video_codec = CONFIG_LIBX264_ENCODER ? + .video_codec = (CONFIG_LIBX264_ENCODER || CONFIG_LIBNVENC_ENCODER) ? AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4, .write_header = mov_write_header, .write_packet = mov_write_packet,