使用ffmpeg dxva技术解码

ffmpeg中已经实现了使用dxva加速解码部分代码。但是要整合在自己的播放器中dxva相关管理代码没有实现。

以下笔者带大家去实现一个基于dxva的加速解码器。

由于文笔不好,主要以代码来说话,望见谅。

一:ffmpeg中实现的dxva解码相关代码介绍

ffmpeg已经实现了h264 h263 mpeg2 wmv3解码的相关代码。关键代码如下

dxva2_h264.c

AVHWAccel ff_h264_dxva2_hwaccel = {
    .name           = "h264_dxva2",
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = CODEC_ID_H264,
    .pix_fmt        = PIX_FMT_DXVA2_VLD,
    .start_frame    = start_frame,
    .decode_slice   = decode_slice,
    .end_frame      = end_frame,
    .priv_data_size = sizeof(struct dxva2_picture_context),
};

dxva2_mpeg2.c

AVHWAccel ff_mpeg2_dxva2_hwaccel = {
    .name           = "mpeg2_dxva2",
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = CODEC_ID_MPEG2VIDEO,
    .pix_fmt        = PIX_FMT_DXVA2_VLD,
    .start_frame    = start_frame,
    .decode_slice   = decode_slice,
    .end_frame      = end_frame,
    .priv_data_size = sizeof(struct dxva2_picture_context),
};

dxva2_vc1.c

#if CONFIG_WMV3_DXVA2_HWACCEL
AVHWAccel ff_wmv3_dxva2_hwaccel = {
    .name           = "wmv3_dxva2",
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = CODEC_ID_WMV3,
    .pix_fmt        = PIX_FMT_DXVA2_VLD,
    .start_frame    = start_frame,
    .decode_slice   = decode_slice,
    .end_frame      = end_frame,
    .priv_data_size = sizeof(struct dxva2_picture_context),
};
#endif

AVHWAccel ff_vc1_dxva2_hwaccel = {
    .name           = "vc1_dxva2",
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = CODEC_ID_VC1,
    .pix_fmt        = PIX_FMT_DXVA2_VLD,
    .start_frame    = start_frame,
    .decode_slice   = decode_slice,
    .end_frame      = end_frame,
    .priv_data_size = sizeof(struct dxva2_picture_context),
};

阅读上述代码,咱们会发现,解码器使用到的dxva_context没有复制,需要通过外部赋值

struct dxva_context {
    /**
     * DXVA2 decoder object
     */
    IDirectXVideoDecoder *decoder;

    /**
     * DXVA2 configuration used to create the decoder
     */
    const DXVA2_ConfigPictureDecode *cfg;

    /**
     * The number of surface in the surface array
     */
    unsigned surface_count;

    /**
     * The array of Direct3D surfaces used to create the decoder
     */
    LPDIRECT3DSURFACE9 *surface;

    /**
     * A bit field configuring the workarounds needed for using the decoder
     */
    uint64_t workaround;

    /**
     * Private to the FFmpeg AVHWAccel implementation
     */
    unsigned report_id;
};

在dxva2api.c(非ffmpeg的源码,由本人实现),中实现对转码的环境变量上下文进行赋值。

static int Setup(va_dxva2_t *va, void **hw, const AVCodecContext *avctx)
{
    //va_dxva2_t *va = vlc_va_dxva2_Get(external);
	unsigned i;
	
    if (va->width == avctx->width&& va->height == avctx->height && va->decoder)
        goto ok;

    /* */
    DxDestroyVideoConversion(va);
    DxDestroyVideoDecoder(va);

    *hw = NULL;
    if (avctx->width <= 0 || avctx->height <= 0)
        return -1;

    if (DxCreateVideoDecoder(va, va->codec_id, avctx))
        return -1;
    /* */
    va->hw.decoder = va->decoder;
    va->hw.cfg = &va->cfg;
    va->hw.surface_count = va->surface_count;
    va->hw.surface = va->hw_surface;
    for (i = 0; i < va->surface_count; i++)
        va->hw.surface[i] = va->surface[i].d3d;

    /* */
    DxCreateVideoConversion(va);

    /* */
ok:
    *hw = &va->hw;
    const d3d_format_t *output = D3dFindFormat(va->output);
    //*chroma = output->codec;
    return 0;
}

另外ffmpeg对阵硬解解码的环境变量赋值定义好了方式

if(is->iUseDxva)
	{
		pCodecCtx->get_buffer = DxGetFrameBuf;
		pCodecCtx->reget_buffer = DxReGetFrameBuf;
		pCodecCtx->release_buffer = DxReleaseFrameBuf;
		pCodecCtx->opaque = NULL;
		//是否为需要硬解
		if(pCodecCtx->codec_id == CODEC_ID_MPEG1VIDEO || pCodecCtx->codec_id == CODEC_ID_MPEG2VIDEO ||
			//avctx->codec_id == CODEC_ID_MPEG4 ||
			pCodecCtx->codec_id == CODEC_ID_H264 ||
			pCodecCtx->codec_id == CODEC_ID_VC1 || pCodecCtx->codec_id == CODEC_ID_WMV3)
		{
			pCodecCtx->get_format = DxGetFormat;
		}

			D3DXSaveSurfaceToFile = NULL;
		hdll = LoadLibrary(TEXT("D3DX9_42.DLL"));
		if(hdll)
			D3DXSaveSurfaceToFile =  (void *)GetProcAddress(hdll,TEXT("D3DXSaveSurfaceToFileA"));
	}

以下为dxva2硬解与ffmpeg接口实现的代码

/*****************************************************************************
 * va.h: Video Acceleration API for avcodec
 *****************************************************************************
 * Copyright (C) 2012 tuyuandong
 *
 * Authors: Yuandong Tu <tuyuandong@gmail.com>
 *
 * This file is part of FFmpeg. 
 *****************************************************************************/
#include <libavutil/pixfmt.h>
#include <libavutil/pixdesc.h>
#include <libavcodec/avcodec.h>
#include <va.h>

enum PixelFormat DxGetFormat( AVCodecContext *avctx,
                                          const enum PixelFormat *pi_fmt )
{
	unsigned int i;
	dxva_t *p_va = (dxva_t*)avctx->opaque;

    if( p_va != NULL )
        dxva_Delete( p_va );

    p_va = dxva_New(avctx->codec_id);
    if(p_va != NULL )
    {
        /* Try too look for a supported hw acceleration */
        for(i = 0; pi_fmt[i] != PIX_FMT_NONE; i++ )
        {
            const char *name = av_get_pix_fmt_name(pi_fmt[i]);
            av_log(NULL,AV_LOG_DEBUG, "Available decoder output format %d (%s)",
                     pi_fmt[i], name ? name : "unknown" );
            if( p_va->pix_fmt != pi_fmt[i] )
                continue;

            /* We try to call dxva_Setup when possible to detect errors when
             * possible (later is too late) */
            if( avctx->width > 0 && avctx->height > 0
             && dxva_Setup(p_va, &avctx->hwaccel_context,avctx) )
            {
                av_log(NULL,AV_LOG_ERROR, "acceleration setup failure" );
                break;
            }

            //if( p_va->description )
            //    av_log(NULL,AV_LOG_INFO, "Using %s for hardware decoding.",
            //              p_va->description );

            /* FIXME this will disable direct rendering
             * even if a new pixel format is renegotiated
             */
            //p_sys->b_direct_rendering = false;
            //p_sys->p_va = p_va;
            avctx->opaque = p_va;
            avctx->draw_horiz_band = NULL;			
            return pi_fmt[i];
        }

        av_log(NULL,AV_LOG_ERROR, "acceleration not available" );
        dxva_Delete( p_va );
    }
    avctx->opaque = NULL;
    /* Fallback to default behaviour */
    return avcodec_default_get_format(avctx, pi_fmt );
}


/*****************************************************************************
 * DxGetFrameBuf: callback used by ffmpeg to get a frame buffer.
 *****************************************************************************
 * It is used for direct rendering as well as to get the right PTS for each
 * decoded picture (even in indirect rendering mode).
 *****************************************************************************/
int DxGetFrameBuf( struct AVCodecContext *avctx,
                               AVFrame *pic )
{
    dxva_t *p_va = (dxva_t *)avctx->opaque;
    //picture_t *p_pic;

    /* */
    pic->reordered_opaque = avctx->reordered_opaque;
    pic->opaque = NULL;

    if(p_va)
    {
        /* hwaccel_context is not present in old ffmpeg version */
        if( dxva_Setup(p_va,&avctx->hwaccel_context, avctx) )
        {
            av_log(NULL,AV_LOG_ERROR, "vlc_va_Setup failed" );
            return -1;
        }

        /* */
        pic->type = FF_BUFFER_TYPE_USER;

#if LIBAVCODEC_VERSION_MAJOR < 54
        pic->age = 256*256*256*64;
#endif

        if(dxva_Get(p_va,pic ) )
        {
            av_log(NULL,AV_LOG_ERROR, "VaGrabSurface failed" );
            return -1;
        }
        return 0;
    }

    return avcodec_default_get_buffer(avctx,pic );
}
int  DxReGetFrameBuf( struct AVCodecContext *avctx, AVFrame *pic )
{
    pic->reordered_opaque = avctx->reordered_opaque;

    /* We always use default reget function, it works perfectly fine */
    return avcodec_default_reget_buffer(avctx, pic );
}

void DxReleaseFrameBuf( struct AVCodecContext *avctx,
                                    AVFrame *pic )
{
    dxva_t *p_va = (dxva_t *)avctx->opaque;
	int i;
	
    if(p_va )
    {
        dxva_Release(p_va, pic );
    }
    else if( !pic->opaque )
    {
        /* We can end up here without the AVFrame being allocated by
         * avcodec_default_get_buffer() if VA is used and the frame is
         * released when the decoder is closed
         */
        if( pic->type == FF_BUFFER_TYPE_INTERNAL )
            avcodec_default_release_buffer( avctx,pic );
    }
    else
    {
        //picture_t *p_pic = (picture_t*)p_ff_pic->opaque;
        //decoder_UnlinkPicture( p_dec, p_pic );
        av_log(NULL,AV_LOG_ERROR,"%d %s a error is rasied\e\n");
    }
    for(i = 0; i < 4; i++ )
        pic->data[i] = NULL;
}

int DxPictureCopy(struct AVCodecContext *avctx,AVFrame *src, AVFrame* dst)
{
	dxva_t *p_va = (dxva_t *)avctx->opaque;
	return dxva_Extract(p_va,src,dst);
}

demo实现的完整代码

/*****************************************************************************
 * OnlyVideo.c: Video Decode Test
 *****************************************************************************
 * Copyright (C) 2012 tuyuandong
 *
 * Authors: Yuandong Tu <tuyuandong@gmail.com>
 *
 * Data 2012-11-28
 *
 * This file is part of FFmpeg. 
 *****************************************************************************/
#include <windows.h>
#include <d3d9.h>
#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "va.h"

typedef struct VideoState {
	int		abort_request;
	char		filename[1024];
	int		srcWidth,srcHeight;	//数据源大小
	int		iUseDxva;		//是否开启DXVA硬解
}VideoState;
char		out_path[128];

void help();
DWORD VideoDecodec(LPVOID lpParameter);

int main(int argc, char *argv[])
{
	VideoState is;
	int i;
	if(argc <2 || (argc == 2 && !strcmp(argv[1],"--help")))
	{
		help();
		return 0;
	}

	memset(&is,0,sizeof(is));
	memset(out_path,0,sizeof(out_path));
	sprintf(is.filename,"%s",argv[1]);
	if(argc > 2 && !strcmp(argv[2],"-dxva"))
		is.iUseDxva = 1;	
	if(argc >4  && !strcmp(argv[3],"-o"))
		strcpy(out_path,argv[4]);
	
	if(out_path[0])
		CreateDirectory(out_path,NULL);
	
	VideoDecodec(&is);
	return 0;
}

void help()
{
    printf("**********************************************\n");
    printf("Usage:\n");
    printf("    OnlyVideo file [options]\n");
    printf("\n");
    printf("Options: \n");
    printf("    -dxva		\n");
    printf("    -o out_path\n");
    printf("\n");
    printf("Examples: \n");
    printf("    OnlyVideo a.mpg -dxva\n");
    printf("**********************************************\n");  
}

void console_log(const char *fmt, ...)
{
	HANDLE h;
	va_list vl;
	va_start(vl, fmt);

	char buf[1024];
	memset(buf,0,1024);
	vsprintf(buf,fmt,vl);

	h = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos;
	ULONG unuse;
	pos.X= 0;
	CONSOLE_SCREEN_BUFFER_INFO bInfo; // 窗口缓冲区信息
	GetConsoleScreenBufferInfo(h, &bInfo );
	pos.Y= bInfo.dwCursorPosition.Y;
	WriteConsoleOutputCharacter(h,buf,strlen(buf),pos,&unuse);
	
    va_end(vl);	
}


typedef enum _D3DXIMAGE_FILEFORMAT
{
    D3DXIFF_BMP         = 0,
    D3DXIFF_JPG         = 1,
    D3DXIFF_TGA         = 2,
    D3DXIFF_PNG         = 3,
    D3DXIFF_DDS         = 4,
    D3DXIFF_PPM         = 5,
    D3DXIFF_DIB         = 6,
    D3DXIFF_HDR         = 7,       //high dynamic range formats
    D3DXIFF_PFM         = 8,       //
    D3DXIFF_FORCE_DWORD = 0x7fffffff

} D3DXIMAGE_FILEFORMAT;

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
	FILE *pFile;
	char szFilename[32];
	int  y;

	// Open file
	if(out_path[0])
		sprintf(szFilename, ".\\%s\\frame%d.ppm",out_path, iFrame);
	else
		sprintf(szFilename, ".\\frame%d.ppm", iFrame);
	
	pFile=fopen(szFilename, "wb");
	if(pFile==NULL)
		return;

	// Write header
	fprintf(pFile, "P6\n%d %d\n255\n", width, height);

	// Write pixel data
	for(y=0; y<height; y++)
		fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);

	// Close file
	fclose(pFile);
}

DWORD VideoDecodec(LPVOID lpParameter)
{
DWORD  (*D3DXSaveSurfaceToFile)(
        char*                    pDestFile,
        D3DXIMAGE_FILEFORMAT      DestFormat,
        LPDIRECT3DSURFACE9        pSrcSurface,
        CONST PALETTEENTRY*       pSrcPalette,
        CONST RECT*               pSrcRect);

	VideoState*		is = lpParameter;
	AVFormatContext *pFormatCtx;
	int             i, videoStream;
	AVCodecContext  *pCodecCtx;
	AVCodec         *pCodec;
	AVFrame         *pFrame; 
	AVFrame        *pFrameYUV = NULL; 
	char*		pYUVBuf =NULL;
	AVPacket        pkt;
	int 			frames;
	DWORD			dwTicks,cost;
	DWORD			sec;
	HINSTANCE	hdll = NULL;

	
	// Register all formats and codecs
	av_register_all();

	// Open video file
	if(av_open_input_file(&pFormatCtx, is->filename, NULL, 0, NULL)!=0)
		return -1; // Couldn't open file

	
	// Retrieve stream information
	//if(av_find_stream_info(pFormatCtx)<0)
	//	return -1; // Couldn't find stream information
	
	// Dump information about file onto standard error
	dump_format(pFormatCtx, 0, is->filename, 0);

	
	// Find the first video stream
	videoStream=-1;
	for(i=0; i<pFormatCtx->nb_streams; i++)
		if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
	{
		videoStream=i;
		break;
	}
	
	if(videoStream==-1)
		return -1; // Didn't find a video stream

	// Get a pointer to the codec context for the video stream
	pCodecCtx=pFormatCtx->streams[videoStream]->codec;

	pCodec=avcodec_find_encoder(CODEC_ID_H264);

	// Find the decoder for the video stream
	pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
	if(pCodec==NULL) {
		fprintf(stderr, "Unsupported codec!\n");
		return -1; // Codec not found
	}

	if(is->iUseDxva)
	{
		pCodecCtx->get_buffer = DxGetFrameBuf;
		pCodecCtx->reget_buffer = DxReGetFrameBuf;
		pCodecCtx->release_buffer = DxReleaseFrameBuf;
		pCodecCtx->opaque = NULL;
		//是否为需要硬解
		if(pCodecCtx->codec_id == CODEC_ID_MPEG1VIDEO || pCodecCtx->codec_id == CODEC_ID_MPEG2VIDEO ||
			//avctx->codec_id == CODEC_ID_MPEG4 ||
			pCodecCtx->codec_id == CODEC_ID_H264 ||
			pCodecCtx->codec_id == CODEC_ID_VC1 || pCodecCtx->codec_id == CODEC_ID_WMV3)
		{
			pCodecCtx->get_format = DxGetFormat;
		}

			D3DXSaveSurfaceToFile = NULL;
		hdll = LoadLibrary(TEXT("D3DX9_42.DLL"));
		if(hdll)
			D3DXSaveSurfaceToFile =  (void *)GetProcAddress(hdll,TEXT("D3DXSaveSurfaceToFileA"));
	}
		
	// Open codec
	if(avcodec_open(pCodecCtx, pCodec)<0)
		return -1; // Could not open codec


	// Allocate video frame
	pFrame=avcodec_alloc_frame();
	pFrameYUV = avcodec_alloc_frame();
	if(pFrameYUV && is->iUseDxva)
	{
		int numBytes=avpicture_get_size(PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height);
		pYUVBuf=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
		pFrameYUV->width = pCodecCtx->width;
		pFrameYUV->height =pCodecCtx->height;
		avpicture_fill((AVPicture *)pFrameYUV, pYUVBuf, PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height);
	}
	

	frames=0;
	cost = 0;
	sec = 0;
	while(!is->abort_request && av_read_frame(pFormatCtx, &pkt)>=0) {
		// Is this a packet from the video stream?
		if(pkt.stream_index==videoStream) {
			int got_picture;
			// Decode video frame
			dwTicks = GetTickCount();
			avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,&pkt);
			cost += (GetTickCount() - dwTicks);
			
			// Did we get a video frame?
			if(got_picture) {	
				frames++;
				if(cost/1000 > sec)
				{
					float speeds= (frames*1000.0)/cost;
					console_log("pic_type[%d] frames[%d] times[%d] speeds[%f fps]\n",
						pFrame->format,frames,cost,speeds);
					sec ++;

					if(pFrame->format == PIX_FMT_DXVA2_VLD )
					{
						//char filename[256] = {0};
						//DWORD ret = 0;
						//LPDIRECT3DSURFACE9 d3d = pFrame->data[3];
						//sprintf(filename,"e://%d.jpg",frames);
						//if(D3DXSaveSurfaceToFile)
						//	ret = D3DXSaveSurfaceToFile(filename,D3DXIFF_JPG/*jpeg*/,d3d,NULL,NULL);
						//filename[128] =  0;
						DxPictureCopy(pCodecCtx,pFrame,pFrameYUV);
						SaveFrame(pFrameYUV,pFrameYUV->width,pFrameYUV->height,frames);
					}
				}
			}
		}
		// Free the packet that was allocated by av_read_frame
		av_free_packet(&pkt);
	}

	avcodec_close(pCodecCtx);
	//av_close_input_file(pFormatCtx);
	if(pYUVBuf)
		av_free(pYUVBuf);
	if(pFrame)
		av_free(pFrame);
	if(pFrameYUV)
		av_free(pFrameYUV);
	if(hdll)
		FreeLibrary(hdll);
}

可执行文件的下载

http://download.csdn.net/detail/tttyd/5166123

源代码下载

http://pan.baidu.com/share/link?shareid=703510495&uk=3240542740

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏腾讯大讲堂的专栏

白底黑字or黑底白字,眼睛更喜欢哪一个?

12010
来自专栏微信公众号:小白课代表

不只是软件,在线也可以免费下载百度文库了。

不管是学生,还是职场员工,下载各种文档几乎是不可避免的,各种XXX.docx,XXX.pptx更是家常便饭,人们最常用的就是百度文库,豆丁文库,道客巴巴这些下载...

44030
来自专栏怀英的自我修炼

考研英语-1-导学

英二图表作文要重视。总体而言,英语一会比英语二难点。不过就写作而言,英语二会比英语一有难度,毕竟图表作文并不好写。

11510
来自专栏腾讯社交用户体验设计

ISUX Xcube智能一键生成H5

50920
来自专栏FSociety

SQL中GROUP BY用法示例

GROUP BY我们可以先从字面上来理解,GROUP表示分组,BY后面写字段名,就表示根据哪个字段进行分组,如果有用Excel比较多的话,GROUP BY比较类...

5.1K20
来自专栏Ken的杂谈

【系统设置】CentOS 修改机器名

17830
来自专栏haifeiWu与他朋友们的专栏

复杂业务下向Mysql导入30万条数据代码优化的踩坑记录

从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负...

27640
来自专栏钱塘大数据

理工男图解零维到十维空间,烧脑已过度,受不了啦!

让我们从一个点开始,和我们几何意义上的点一样,它没有大小、没有维度。它只是被想象出来的、作为标志一个位置的点。它什么也没有,空间、时间通通不存在,这就是零维度。

28430
来自专栏前端桃园

知识体系解决迷茫的你

最近在星球里群里都有小伙伴说道自己对未来的路比较迷茫,一旦闲下来就不知道自己改干啥,今天我这篇文章就是让你觉得一天给你 25 个小时你都不够用,觉得睡觉都是浪费...

20240
来自专栏钱塘大数据

中国互联网协会发布:《2018中国互联网发展报告》

在2018中国互联网大会闭幕论坛上,中国互联网协会正式发布《中国互联网发展报告2018》(以下简称《报告》)。《中国互联网发展报告》是由中国互联网协会与中国互联...

13450

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励