#FFmpeg關(guān)于Nvidia支持介紹
##NVDEC/CUVID(官方介紹如下)
官方鏈接:http://trac.ffmpeg.org/wiki/HWAccelIntro
CUDA (NVENC/NVDEC)
NVENC and NVDEC are NVIDIA’s hardware-accelerated encoding and decoding APIs. They used to be called CUVID. They can be used for encoding and decoding on Windows and Linux. FFmpeg refers to NVENC/NVDEC interconnect as CUDA.
NVDEC offers decoders for H.264, HEVC, MJPEG, MPEG-1/2/4, VP8/VP9, VC-1. Codec support varies by hardware (see the ?GPU compatibility table).
Note that FFmpeg offers both NVDEC and CUVID hwaccels. They differ in how frames are decoded and forwarded in memory.
The full set of codecs being available only on Pascal hardware, which adds VP9 and 10 bit support. The note about missing ffnvcodec from NVENC applies for NVDEC as well.
Sample decode using CUDA:
ffmpeg -hwaccel cuda -i input output
Sample decode using CUVID:
ffmpeg -c:v h264_cuvid -i input output
FFplay only supports older option -vcodec (not -c:v) and only CUVID.
ffplay -vcodec hevc_cuvid file.mp4
Full hardware transcode with NVDEC and NVENC:
ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i input -c:v h264_nvenc -preset slow output
If ffmpeg was compiled with support for libnpp, it can be used to insert a GPU based scaler into the chain:
ffmpeg -hwaccel_device 0 -hwaccel cuda -i input -vf scale_npp=-1:720 -c:v h264_nvenc -preset slow output.mkv
The -hwaccel_device option can be used to specify the GPU to be used by the hwaccel in ffmpeg.
上面一段話(huà)的總結(jié)是,我們有兩種方式去調(diào)用h264的解碼,第一種是通過(guò)加速器-hwaccel cuda
去調(diào)用,第二種是通過(guò)-c:v h264_cuvid
,這兩種方式都是GPU解碼,底層調(diào)用的都是ffnvcodec的API,只是調(diào)用方式不同而已。
總結(jié)一下:
cuvid和nvdec底層調(diào)用的解碼API都是ffnvcodec中提供的API,兩者本質(zhì)沒(méi)有上面區(qū)別。
在調(diào)用區(qū)別是:
avcodec_find_decoder_by_name(h264_cuvid、libx265等)
直接獲取到一個(gè)解碼器,這個(gè)解碼器內(nèi)部使用的是ffnvcodec的API來(lái)解碼。AVCodecContext
指定一個(gè)加速硬件,比如cuda
,然后在實(shí)際使用過(guò)程中,如果發(fā)現(xiàn)指定了硬件加速器,那么就進(jìn)入cuda的解碼器中,也就是ffnvcodec的API中,如果沒(méi)有加速器,進(jìn)進(jìn)入ffmpeg自己寫(xiě)的cpu的軟解碼的邏輯中。綜上所述,cuvid和nvenc是Nvidia的第三方編解碼庫(kù)(你以前是不是覺(jué)的nvdec和nvenc是Nvidia的第三方解碼器),nvdec是解碼的加速器,就是ffmpeg內(nèi)部自己寫(xiě)了一個(gè)h264的解碼代碼(根據(jù)h264標(biāo)準(zhǔn)),在這些代碼中內(nèi)嵌了一個(gè)硬解碼加速器,比如cuda,如果你指定了使用cuda硬件,那么就會(huì)跳入硬解碼的邏輯中。
下面詳細(xì)介紹一下
目前FFmpeg的第三方庫(kù)支持中有關(guān)英偉達(dá)的支持有如下幾個(gè),注意后面的[autodetect]
表示不指定disable就自動(dòng)檢測(cè):
The following libraries provide various hardware acceleration features:
--disable-cuvid disable Nvidia CUVID support [autodetect]
--disable-ffnvcodec disable dynamically linked Nvidia code [autodetect]
--disable-nvdec disable Nvidia video decoding acceleration (via hwaccel) [autodetect]
--disable-nvenc disable Nvidia video encoding code [autodetect]
##那么這四個(gè)有什么聯(lián)系和區(qū)別呢?
下面是configure中硬件加速自動(dòng)檢測(cè)的列表,可以看到有我們剛才說(shuō)的四個(gè)NVIDIA模塊。
HWACCEL_AUTODETECT_LIBRARY_LIST=' ... cuda cuvid ... ffnvcodec nvdec nvenc ... ' AUTODETECT_LIBS=' $EXTERNAL_AUTODETECT_LIBRARY_LIST $HWACCEL_AUTODETECT_LIBRARY_LIST $THREADS_LIST '
下面是自動(dòng)檢測(cè)的流程,其實(shí)就是檢查頭文件、庫(kù)文件是否存在,能否通過(guò)編譯(一個(gè)簡(jiǎn)單的main函數(shù))
#下面是檢測(cè)ffnvcodec開(kāi)關(guān)以及自動(dòng)檢測(cè)其頭文件和庫(kù)文件是否可以用 #ffnvcodec是Nvidia提供的關(guān)于編解碼的頭文件 if ! disabled ffnvcodec; then ffnv_hdr_list='ffnvcodec/nvEncodeAPI.h ffnvcodec/dynlink_cuda.h ffnvcodec/dynlink_cuviddec.h ffnvcodec/dynlink_nvcuvid.h' check_pkg_config ffnvcodec 'ffnvcodec >= 9.1.23.1' '$ffnv_hdr_list' '' || \ check_pkg_config ffnvcodec 'ffnvcodec >= 9.0.18.3 ffnvcodec < 9.1' '$ffnv_hdr_list' '' || \ check_pkg_config ffnvcodec 'ffnvcodec >= 8.2.15.10 ffnvcodec < 8.3' '$ffnv_hdr_list' '' || \ check_pkg_config ffnvcodec 'ffnvcodec >= 8.1.24.11 ffnvcodec < 8.2' '$ffnv_hdr_list' '' fi #查看編碼頭文件ffnvcodec/nvEncodeAPI.h和庫(kù)文件ffnvcodec是否可以通過(guò)編譯 enabled nvenc && test_cc -I$source_path <<EOF || disable nvenc #include <ffnvcodec/nvEncodeAPI.h> NV_ENCODE_API_FUNCTION_LIST flist; void f(void) { struct { const GUID guid; } s[] = { { NV_ENC_PRESET_HQ_GUID } }; } int main(void) { return 0; } EOF #這里同上,檢測(cè)頭文件ffnvcodec/dynlink_cuda.h ffnvcodec/dynlink_cuviddec.h是否存在 if enabled_any nvdec cuvid; then check_type 'ffnvcodec/dynlink_cuda.h ffnvcodec/dynlink_cuviddec.h' 'CUVIDAV1PICPARAMS' fi
在上面的解碼模塊中有一個(gè)命令enabled_any nvdec cuvid
從這里可以看到(它倆使用的是相同的頭文件)nvdec和cuvid
最終依賴(lài)的是一個(gè)底層庫(kù)。
接下來(lái)檢測(cè)上述檢測(cè)是否通過(guò)
enabled(){ test '${1#!}' = '$1' && op='=' || op='!=' eval test 'x\$${1#!}' $op 'xyes' } requested(){ test '${1#!}' = '$1' && op='=' || op='!=' eval test 'x\$${1#!}_requested' $op 'xyes' } # Check if requested libraries were found. for lib in $AUTODETECT_LIBS; do requested $lib && ! enabled $lib && die 'ERROR: $lib requested but not found'; done
##FFmpeg源代碼分析
下面是cuviddec,c解碼器模板內(nèi)容:
// * Nvidia CUVID decoder #include 'libavutil/hwcontext.h' #include 'compat/cuda/dynlink_loader.h' #include 'avcodec.h' #include 'decode.h' #include 'hwconfig.h' #include 'nvdec.h' #include 'internal.h' static av_cold int cuvid_decode_init(AVCodecContext *avctx); //這里是一個(gè)宏定義模板 #define DEFINE_CUVID_CODEC(x, X, bsf_name) static const AVClass x##_cuvid_class = { .class_name = #x '_cuvid', .item_name = av_default_item_name, .option = options, .version = LIBAVUTIL_VERSION_INT, }; const AVCodec ff_##x##_cuvid_decoder = { .name = #x '_cuvid', .long_name = NULL_IF_CONFIG_SMALL('Nvidia CUVID ' #X ' decoder'), .type = AVMEDIA_TYPE_VIDEO, .id = AV_CODEC_ID_##X, .priv_data_size = sizeof(CuvidContext), .priv_class = &x##_cuvid_class, .init = cuvid_decode_init, .close = cuvid_decode_end, .receive_frame = cuvid_output_frame, .flush = cuvid_flush, .bsfs = bsf_name, .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_HARDWARE, .caps_internal = FF_CODEC_CAP_SETS_FRAME_PROPS, .pix_fmts = (const enum AVPixelFormat[]){ AV_PIX_FMT_CUDA, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, AV_PIX_FMT_P016, AV_PIX_FMT_NONE }, .hw_configs = cuvid_hw_configs, .wrapper_name = 'cuvid', };
上面cuvid_decode_init 、uvid_decode_end
這些回調(diào)函數(shù)內(nèi)部使用的就是ffnvcodec中的API.
然后再看編碼器,這里是Nvidia編碼器nvenc.h
#include <ffnvcodec/nvEncodeAPI.h> #include 'compat/cuda/dynlink_loader.h' int ff_nvenc_encode_init(AVCodecContext *avctx); int ff_nvenc_encode_close(AVCodecContext *avctx); int ff_nvenc_receive_packet(AVCodecContext *avctx, AVPacket *pkt); void ff_nvenc_encode_flush(AVCodecContext *avctx); extern const enum AVPixelFormat ff_nvenc_pix_fmts[]; extern const AVCodecHWConfigInternal *const ff_nvenc_hw_configs[];
這里是nvenc_h264.c,英偉達(dá)關(guān)于H264的編碼器
static const AVClass h264_nvenc_class = { .class_name = 'h264_nvenc', .item_name = av_default_item_name, .option = options, .version = LIBAVUTIL_VERSION_INT, }; const AVCodec ff_h264_nvenc_encoder = { .name = 'h264_nvenc', .long_name = NULL_IF_CONFIG_SMALL('NVIDIA NVENC H.264 encoder'), .type = AVMEDIA_TYPE_VIDEO, .id = AV_CODEC_ID_H264, .init = ff_nvenc_encode_init, .receive_packet = ff_nvenc_receive_packet, .close = ff_nvenc_encode_close, .flush = ff_nvenc_encode_flush, .priv_data_size = sizeof(NvencContext), .priv_class = &h264_nvenc_class, .defaults = defaults, .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_HARDWARE | AV_CODEC_CAP_ENCODER_FLUSH | AV_CODEC_CAP_DR1, .caps_internal = FF_CODEC_CAP_INIT_CLEANUP, .pix_fmts = ff_nvenc_pix_fmts, .wrapper_name = 'nvenc', .hw_configs = ff_nvenc_hw_configs, };
上面ff_nvenc_receive_packet、ff_nvenc_encode_close
這些回調(diào)函數(shù)內(nèi)部使用的就是ffnvcodec中的API.
Nvidia支持的加速編碼碼還包含:
上面的兩個(gè)我們稱(chēng)之為編解碼器,是因?yàn)闃?gòu)造他它們的結(jié)構(gòu)體是AVCodec
,它們都注冊(cè)在編解碼器中的數(shù)組中:
static const FFCodec * const codec_list[] = { &ff_h264_nvenc_encoder;, &ff_hevc_cuvid_decoder, &ff_libx264_encoder, &ff_amv_encoder, ... &ff_apng_decoder, &ff_arbc_decoder, &ff_argo_decoder, &ff_asv1_decoder, &ff_adpcm_ima_ws_decoder, &ff_adpcm_ms_decoder, &ff_adpcm_mtaf_decoder, &ff_adpcm_psx_decoder, &ff_adpcm_sbpro_2_decoder, &ff_bintext_decoder, &ff_xbin_decoder, &ff_idf_decoder, &ff_av1_decoder, NULL };
那下面這個(gè)就是加速器,它是由AVHWAccel
構(gòu)成的
這里是nvdec.h,里面是NVIDIA解碼sdk的封裝
// * HW decode acceleration through NVDEC typedef struct NVDECContext ; typedef struct NVDECFrame; #include 'compat/cuda/dynlink_loader.h' int ff_nvdec_decode_init(AVCodecContext *avctx); int ff_nvdec_decode_uninit(AVCodecContext *avctx); int ff_nvdec_start_frame(AVCodecContext *avctx, AVFrame *frame); int ff_nvdec_start_frame_sep_ref(AVCodecContext *avctx, AVFrame *frame, int has_sep_ref); int ff_nvdec_end_frame(AVCodecContext *avctx); int ff_nvdec_simple_end_frame(AVCodecContext *avctx); int ff_nvdec_simple_decode_slice(AVCodecContext *avctx, const uint8_t *buffer, uint32_t size); int ff_nvdec_frame_params(AVCodecContext *avctx, AVBufferRef *hw_frames_ctx, int dpb_size, int supports_444); int ff_nvdec_get_ref_idx(AVFrame *frame); typedef struct H264Context { const AVClass *class; AVCodecContext *avctx; ... } typedef struct AVCodecContext { /** * Hardware accelerator in use * - encoding: unused. * - decoding: Set by libavcodec */ const struct AVHWAccel *hwaccel; ... } const AVHWAccel ff_h264_nvdec_hwaccel = { .name = 'h264_nvdec', .type = AVMEDIA_TYPE_VIDEO, .id = AV_CODEC_ID_H264, .pix_fmt = AV_PIX_FMT_CUDA, .start_frame = nvdec_h264_start_frame, .end_frame = ff_nvdec_end_frame, .decode_slice = nvdec_h264_decode_slice, .frame_params = nvdec_h264_frame_params, .init = ff_nvdec_decode_init, .uninit = ff_nvdec_decode_uninit, .priv_data_size = sizeof(NVDECContext), };
上面nvdec_h264_start_frame、nvdec_h264_frame_params
這些回調(diào)函數(shù)內(nèi)部使用的就是ffnvcodec中的API.
Nvidia支持的加速解碼還包含:
然后來(lái)看看在ffmpeg內(nèi)部解碼器中是怎么調(diào)用加速器的,下面是編解碼器的上下文,
在struct AVCodecContext中有這么一個(gè)成員變量
/** * Hardware accelerator in use * - encoding: unused. * - decoding: Set by libavcodec */ const struct AVHWAccel *hwaccel; AVBufferRef *hw_device_ctx;
如果你在打開(kāi)ffmpeg提供的解碼器時(shí),指定了加速器cuda
,那么就會(huì)在下面調(diào)用中進(jìn)入硬件加速解碼
這些函數(shù)實(shí)際在h264.c中調(diào)用:
static int decode_nal_units(H264Context *h, const uint8_t *buf, int buf_size){ ... if (h->nb_slice_ctx_queued == max_slice_ctx) { if (h->avctx->hwaccel) { ret = avctx->hwaccel->decode_slice(avctx, nal->raw_data, nal->raw_size); h->nb_slice_ctx_queued = 0; } ... } ... } static int decode_nal_units(H264Context *h, const uint8_t *buf, int buf_size){ ... case H264_NAL_SPS: { GetBitContext tmp_gb = nal->gb; if (avctx->hwaccel && avctx->hwaccel->decode_params) { ret = avctx->hwaccel->decode_params(avctx, nal->type, nal->raw_data, nal->raw_size); if (ret < 0) goto end; } ... }
參考:http://trac.ffmpeg.org/wiki/HWAccelIntro
聯(lián)系客服