前面介紹利用NVIDIA公司提供的CUVID庫進行視頻硬解碼,下面將介紹利用DXVA進行硬解碼。
一、DXVA介紹
DXVA是微軟公司專門定制的視頻加速規范,是一種接口規范。DXVA規范制定硬件加速解碼可分四級:VLD,控制BitStream;IDCT,反余弦變換;Mocomp,運動補償,Pixel Prediction;PostProc,顯示后處理。其中,VLD加速等級最高,所以其包含IDCT、MoCoopm和PostProc;IDCT加速次之,包含MoCoopm和PostProc;最后MoComp加速僅包含PostProc。一款顯卡芯片在硬件支持DXVA規范,並不代表它就實現了DXVA所有功能。DXVA_Checker可用於檢測硬件所支持的等級,DXVA_Checker運行示意圖如下所示。
二、使用FFmpeg中DXVA技術硬解碼
基本思路:
1.根據FFmpeg對編碼器的描述,實現自定義的硬解碼器。
2.通過REGISTER_ENCODEC(X,x)將自定義的視頻編碼器添加到視頻編解碼器。
3.在視頻解碼,根據編碼器ID或編碼器名稱找到視頻編解碼器中自定義的視頻解碼器。
4.利用自定義的視頻解碼器,解碼視頻。
其關鍵步驟是:自定義解碼器的實現,需要參考FFmpeg源碼中,解碼器的定義和接口設計。
基於DXVA的自定義解碼器實現
1.熟悉FFmpeg中編解碼的組織方式
下圖是ffmpeg編解碼組織的簡單示意圖。
由示意圖可知,編解碼器由全局鏈表組織,可根據編碼器的名稱或ID,獲取編解碼器。
編解碼器的具體編解碼的具體工作,由編解碼器定義的函數指針完成。
自定義解碼器時,需要按照AVCodec結構體,定義解碼器的屬性,然后注冊到全局編解碼器鏈表中。
2.基於DXVA解碼器的定義實現
ff_h264_dxva2_decoder的定義如下:
1 AVCodec ff_h264_dxva2_decoder = { 2 .name = "h264_dxva2", 3 .type = AVMEDIA_TYPE_VIDEO, 4 .id = AV_CODEC_ID_H264, 5 .priv_data_size = sizeof(DXVA2_DecoderContext), 6 .init = h264_dxva2dec_init, 7 .close = h264_dxva2dec_close, 8 .decode = h264_dxva2dec_decode, 9 .capabilities = CODEC_CAP_DELAY, 10 .flush = h264_dxva2dec_flush, 11 .long_name = NULL_IF_CONFIG_SMALL("H.264 (DXVA2 acceleration)"), 12 };
ff_h264_dxva2_decoder的函數指針對應的函數定義如下:
1 static int h264_dxva2dec_decode(AVCodecContext *avctx, void *data, int *got_frame, 2 AVPacket *avpkt) 3 { 4 return ff_dxva2dec_decode(avctx,data,got_frame,avpkt,&ff_h264_decoder); 5 } 6 7 static av_cold int h264_dxva2dec_close(AVCodecContext *avctx) 8 { 9 return ff_dxva2dec_close(avctx,&ff_h264_decoder); 10 } 11 12 static av_cold int h264_dxva2dec_init(AVCodecContext *avctx) 13 { 14 return ff_dxva2dec_init(avctx,&ff_h264_dxva2_decoder,&ff_h264_decoder); 15 } 16 17 static void h264_dxva2dec_flush(AVCodecContext *avctx) 18 { 19 ff_dxva2dec_flush(avctx,&ff_h264_decoder); 20 }
上述代碼,只是ff_dxva2dec_init(),ff_dxva2dec_flush(),ff_dxva2dec_decode(),ff_dxva2dec_close()的封裝,具體解碼的實現,由ff_dxva2dec_xxx相關函數完成,其代碼實現如下:

1 static int get_buffer(struct AVCodecContext *avctx, AVFrame *pic) 2 { 3 int ret; 4 DXVA2_DecoderContext *ctx = (DXVA2_DecoderContext *)avctx->priv_data; 5 dxva2_context *dxva2_ctx = &ctx->dxva2_ctx; 6 avctx->pix_fmt = ctx->pix_fmt; 7 ff_init_buffer_info(avctx, pic); 8 if ((ret = ctx->get_buffer(avctx,pic)) < 0) { 9 av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n"); 10 return ret; 11 } 12 if (dxva2_ctx) { 13 if (av_get_dxva2_surface(dxva2_ctx, pic)) { 14 av_log(NULL, AV_LOG_ERROR, "VaGrabSurface failed"); 15 return -1; 16 } 17 return 0; 18 } else { 19 av_log(NULL, AV_LOG_ERROR, "No dxva2 context, get buffer failed"); 20 return -1; 21 } 22 } 23 24 static void release_buffer(struct AVCodecContext *avctx, AVFrame *pic) 25 { 26 DXVA2_DecoderContext *ctx = (DXVA2_DecoderContext *)avctx->priv_data; 27 dxva2_context *dxva2_ctx = &ctx->dxva2_ctx; 28 if (dxva2_ctx) { 29 av_release_dxva2_surface(dxva2_ctx, pic); 30 } 31 ctx->release_buffer(avctx,pic); 32 for (int i = 0; i < 4; i++) 33 pic->data[i] = NULL; 34 } 35 36 static enum PixelFormat get_format(AVCodecContext *p_context, 37 const enum PixelFormat *pi_fmt) 38 { 39 return AV_PIX_FMT_DXVA2_VLD; 40 } 41 static int check_format(AVCodecContext *avctx) 42 { 43 uint8_t *pout; 44 int psize; 45 int index; 46 H264Context *h; 47 int ret = -1; 48 AVCodecParserContext *parser = NULL; 49 /* check if support */ 50 switch (avctx->codec_id) { 51 case AV_CODEC_ID_H264: 52 /* init parser & parse file */ 53 parser = av_parser_init(avctx->codec->id); 54 if (!parser) { 55 av_log(avctx, AV_LOG_ERROR, "Failed to open parser.\n"); 56 break; 57 } 58 parser->flags = PARSER_FLAG_COMPLETE_FRAMES; 59 index = av_parser_parse2(parser, avctx, &pout, &psize, NULL, 0, 0, 0, 0); 60 if (index < 0) { 61 av_log(avctx, AV_LOG_ERROR, "Failed to parse this file.\n"); 62 av_parser_close(parser); 63 } 64 h = parser->priv_data; 65 if (8 == h->sps.bit_depth_luma) { 66 if (!CHROMA444 && !CHROMA422) { 67 // only this will decoder switch to hwaccel 68 av_parser_close(parser); 69 ret = 0; 70 break; 71 } 72 } else { 73 av_log(avctx, AV_LOG_ERROR, "Unsupported file.\n"); 74 av_parser_close(parser); 75 break; 76 } 77 break; 78 case AV_CODEC_ID_MPEG2VIDEO: 79 if (CHROMA_420 == get_mpeg2_video_format(avctx)) { 80 ret = 0; 81 break; 82 } else { 83 av_log(avctx, AV_LOG_ERROR, "Unsupported file.\n"); 84 break; 85 } 86 default: 87 ret = 0; 88 break; 89 } 90 return ret; 91 } 92 93 int ff_dxva2dec_decode(AVCodecContext *avctx, void *data, int *got_frame, 94 AVPacket *avpkt,AVCodec *codec) 95 { 96 DXVA2_DecoderContext *ctx = (DXVA2_DecoderContext *)avctx->priv_data; 97 AVFrame *pic = data; 98 int ret; 99 ret = codec->decode(avctx, data, got_frame, avpkt); 100 if (*got_frame) { 101 pic->format = ctx->pix_fmt; 102 av_extract_dxva2(&(ctx->dxva2_ctx),pic); 103 } 104 avctx->pix_fmt = ctx->pix_fmt; 105 return ret; 106 } 107 108 int ff_dxva2dec_close(AVCodecContext *avctx,AVCodec *codec) 109 { 110 DXVA2_DecoderContext *ctx = avctx->priv_data; 111 /* release buffers and decoder */ 112 av_release_dxva2(&ctx->dxva2_ctx); 113 /* close decoder */ 114 codec->close(avctx); 115 return 0; 116 } 117 118 119 int ff_dxva2dec_init(AVCodecContext *avctx,AVCodec *hwcodec,AVCodec *codec) 120 { 121 DXVA2_DecoderContext *ctx = (DXVA2_DecoderContext *)avctx->priv_data; 122 dxva2_context *dxva2_ctx = (dxva2_context *)(&ctx->dxva2_ctx); 123 int ret; 124 ctx->initialized = 0; 125 /* init pix_fmts of codec */ 126 if (!(hwcodec->pix_fmts)) { 127 hwcodec->pix_fmts = dxva2_pixfmts; 128 } 129 /* check if DXVA2 supports this file */ 130 if (check_format(avctx) < 0) 131 goto failed; 132 133 /* init vda */ 134 memset(dxva2_ctx, 0, sizeof(dxva2_context)); 135 ret = av_create_dxva2(avctx->codec_id,dxva2_ctx); 136 if (ret < 0) { 137 av_log(NULL,AV_LOG_ERROR,"create dxva2 error\n"); 138 return 0; 139 } 140 ctx->pix_fmt = avctx->get_format(avctx, avctx->codec->pix_fmts); 141 ret = av_setup_dxva2(dxva2_ctx, &avctx->hwaccel_context, &avctx->pix_fmt, avctx->width, avctx->height); 142 if (ret < 0) { 143 av_log(NULL,AV_LOG_ERROR,"error DXVA setup %d\n", ret); 144 goto failed; 145 } 146 /* changes callback functions */ 147 ctx->get_buffer = avctx->get_buffer; 148 avctx->get_format = get_format; 149 avctx->get_buffer = get_buffer; 150 avctx->release_buffer = release_buffer; 151 /* init decoder */ 152 ret = codec->init(avctx); 153 if (ret < 0) { 154 av_log(avctx, AV_LOG_ERROR, "Failed to open decoder.\n"); 155 goto failed; 156 } 157 ctx->initialized = 1; 158 return 0; 159 failed: 160 ff_dxva2dec_close(avctx,codec); 161 return -1; 162 } 163 164 void ff_dxva2dec_flush(AVCodecContext *avctx,AVCodec *codec) 165 { 166 return codec->flush(avctx); 167 }
其中,在ff_dxva2dec_init()函數中,利用av_create_dxva2()函數創建dxva2_context,av_setup_dxva2()設置dxva2_context。
在ff_dxva2dec_close()函數中,利用av_release_dxva2()釋放dxva2_context。
av_xxx_dxva2()相關函數,主要利用DXVA2的API接口,創建dxva2的上下文,並進行管理。
總體而言,經過四次封裝,形成方便的硬解碼接口。
DXVA2 API接口 ---> av_xxx_dxva2 ---> ff_dxva2dec_xxx ---> h264_dxva2dec_xxx ---> ff_h264_dxva2_decoder
參考資料: