Directshow使用ffmpeg构建解码filter

Directshow作为windows平台的多媒体开发框架,个人感觉还是不错的。

我做的这个filter是继承于CTransformFilter类,单输入单输出,因为使用的是ffmpeg来解码,因此支持的解码类型就可以多种了,例如mpeg4,avc,mpeg2,我要描述的是关于mpep2-video的解码,闲言少叙,且看代码。

CTransformFilter是Directshow sdk提供的基类,是用来简化单输入单输出filter的开发,尤其是编解码可以继承于该类,来简化开发。

继承CTransformFilter类,需要实现其纯虚函数,查看源码transfrm.h,如下:

    // =================================================================
    // ----- override these bits ---------------------------------------
    // =================================================================

    // These must be supplied in a derived class

    virtual HRESULT Transform(IMediaSample * pIn, IMediaSample *pOut);

    // check if you can support mtIn
    virtual HRESULT CheckInputType(const CMediaType* mtIn) PURE;

    // check if you can support the transform from this input to this output
    virtual HRESULT CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut) PURE;

    // this goes in the factory template table to create new instances
    // static CCOMObject * CreateInstance(__inout_opt LPUNKNOWN, HRESULT *);

    // call the SetProperties function with appropriate arguments
    virtual HRESULT DecideBufferSize(
                        IMemAllocator * pAllocator,
                        __inout ALLOCATOR_PROPERTIES *pprop) PURE;

    // override to suggest OUTPUT pin media types
    virtual HRESULT GetMediaType(int iPosition, __inout CMediaType *pMediaType) PURE;

那就开始一个个贴代码吧,顺带再说一句CTransformFilter已经帮我们实现了两个pin,一个输入pin,一个输出pin,在其源码的定义里很清楚。


1. CheckInputType,检查输入格式     

HRESULT	CXX::CheckInputType(const CMediaType * mtIn)
{
	CheckPointer(mtIn,E_POINTER);
	if((mtIn->majortype == MEDIATYPE_Video)
	&& (mtIn->subtype == MEDIASUBTYPE_MPEG2_VIDEO)
	&& (mtIn->formattype == FORMAT_MPEG2Video))	
	{
		return S_OK;
	}
	return VFW_E_TYPE_NOT_ACCEPTED;
}

很简单,判断输入类型是否是mpeg2-video。


2.CheckTransform,检查输入类型CheckInputType,并且检查输出类型,这里指定为I420或YV12

HRESULT	CXX::CheckTransform(const CMediaType * mtIn, const CMediaType * mtOut)
{
	HRESULT hr;

	if(FAILED(hr = CheckInputType(mtIn)))
	{
		return hr;
	}

	if(*mtOut->Type() != MEDIATYPE_Video)
	{
		return VFW_E_TYPE_NOT_ACCEPTED;
	}
	if(*mtOut->Subtype() != MEDIASUBTYPE_IYUV)
	{
		return VFW_E_TYPE_NOT_ACCEPTED;
	}
	if(*mtOut->Subtype() != MEDIASUBTYPE_YV12)
	{
		return VFW_E_TYPE_NOT_ACCEPTED;
	}
}
 

 

3. DecideBufferSize,设置buffer的大小及数目,并验证设置是否生效

HRESULT	CXX::DecideBufferSize(IMemAllocator * pAlloc, ALLOCATOR_PROPERTIES * pProperties)
{
	HRESULT hr;

	AM_MEDIA_TYPE mt;
	hr = m_pOutput->ConnectionMediaType(&mt);
	if(FAILED(hr))
	{
		return hr;
	}
	int imagesize = 0;
	if(mt.formattype == FORMAT_VideoInfo)
		//VIDEOINFOHEADER * pvih = (VIDEOINFOHEADER *)mt.pbFormat;
		//BITMAPINFOHEADER * pbih = &pvih->bmiHeader;
		//imagesize = pbih->biSizeImage;
		imagesize = VIDEO_FRAME_MAX_SIZE;//这里用的固定大小,类似于1920*1080*4,上述注释的代码可以根据输出pin的媒体类型来计算实际的buffer大小
	}else if(mt.formattype == FORMAT_VideoInfo2)
	{
		//VIDEOINFOHEADER2 * pvih2 = (VIDEOINFOHEADER2 *)mt.pbFormat;
		//BITMAPINFOHEADER * pbih = &pvih2->bmiHeader;
		//imagesize = pbih->biSizeImage;
		imagesize = VIDEO_FRAME_MAX_SIZE;//同上注释
	}else
	{
		ASSERT(FALSE);
	}
	pProperties->cbBuffer = imagesize * 2;
	if(pProperties->cBuffers == 0)
	{
		pProperties->cBuffers = 1;
	}
	FreeMediaType(mt);

	if(pProperties->cbAlign == 0)
	{
		pProperties->cbAlign = 1;
	}
	ALLOCATOR_PROPERTIES Actual;
	hr = pAlloc->SetProperties(pProperties, &Actual);
	if(FAILED(hr)) 
	{
		return hr;
	}

	if(Actual.cbBuffer < pProperties->cbBuffer) 
	{
		return E_FAIL;
	}
		
	return S_OK;
}


4.Transform,实际工作的地方,把输入的码流通过ffmpeg转换为yuv图像,这段代码有点多啊

HRESULT	CXX::Transform(IMediaSample * pIn, IMediaSample * pOut)
{
	HRESULT	hr;

	int frameSize = pIn->GetActualDataLength();
	BYTE * frameData;
	hr = pIn->GetPointer(&frameData);//获取输入码流数据
	if(FAILED(hr))
	{
		return hr;
	}

	long lDestSize = pOut->GetSize();
	BYTE * pDst;
	hr = pOut->GetPointer(&pDst);//获取输出数据指针
	if(FAILED(hr))
	{
		return hr;
	}

	int frameFinished;  
	int init = 0;
	int width = 0;
	int height = 0;
	int decSize	= lDestSize;

	AVPacket packet;//ffmpeg相关  
	av_init_packet(&packet); 

	packet.size = frameSize;//设置输入数据参数
	packet.data = frameData;

 
	if (packet.size > 0)
	{ 
		decSize = avcodec_decode_video2(mpCodecCtx, mpFrame, &frameFinished, &packet);  
		if (decSize < 0)
		{
			return E_FAIL;
		}
		else
		{
			if (frameFinished)
			{
				memset(mFrameBuff, 0x0, sizeof(mFrameBuff));
				unsigned char *buf = mFrameBuff;  

				int height = mpFrame->height;  
				int width = mpFrame->width;  
				decSize = height * width * 3 / 2;
 
				int a=0,i;   

				for (i=0; i<height; i++)   
				{   
					memcpy(buf+a,mpFrame->data[0] + i * mpFrame->linesize[0], width);   
					a+=width;   
				}   
				for (i=0; i<height/2; i++)   
				{
					memcpy(buf+a,mpFrame->data[2] + i * mpFrame->linesize[2], width/2);/* 这边要说明下,我的mpeg2-video解码后的图像在mpFrmae->data里uv反了,因此下面拷贝数据的地方先是data[2]再是data[1],最终的结果是拼接成一帧I420数据*/
					a+=width/2;   
				}
				for (i=0; i<height/2; i++)   
				{   
					memcpy(buf+a,mpFrame->data[1] + i * mpFrame->linesize[1], width/2);   
					a+=width/2;   
				}   
  
	
				//FILE* testDat = fopen("e:/test.dat", "ab+");
				//fwrite(mFrameBuff, decSize, 1, testDat);
				//fclose(testDat);
			}
			else
			{
				return E_FAIL;
			}
		}
	}
	else
	{
		return E_FAIL;
	}
	
	if(mInputFormatId != mOutputFormatId)//ffmpeg解码出来的是I420,可能render需要的是YV12,在这里做转换 
	{
		memset(mTempBuff, 0x0, sizeof(mTempBuff));
		int currentSize = 0;
		_changeFormat(mInputFormatId
			, mOutputFormatId
			, mWidth
			, mHeight
			, mIsTopBottom
			, mFrameBuff
			, mTempBuff
			, ¤tSize);
		memset(mFrameBuff, 0x0, sizeof(mFrameBuff));
		memmove(mFrameBuff, mTempBuff, currentSize);
	}


	if(m_lPitchWidth)//render filter可能要求实际图像字节对齐,在这里做宽度拉伸
	{	
		memset(mTempBuff, 0x0, sizeof(mTempBuff));
		_changePitch(mOutputFormatId
			, mHeight		
			, mWidth
			, mFrameBuff
			, m_lPitchWidth
			, mTempBuff
			, &decSize); 
		memmove(pDst, mTempBuff, decSize);
	}
	else
	{
		memmove(pDst, mFrameBuff, decSize); 
	}


	hr = pOut->SetActualDataLength(decSize);
	if(FAILED(hr))
	{
		return hr;
	}


	REFERENCE_TIME rtStart;
	REFERENCE_TIME rtStop;
	pIn->GetTime(&rtStart, &rtStop);
	pOut->SetTime(&rtStart, &rtStop);




	pOut->SetDiscontinuity(FALSE);
	pOut->SetPreroll(FALSE);
	pOut->SetSyncPoint(TRUE);




	return S_OK;
}

上面的格式转换,以及宽度拉伸的函数由于是第三方库,因此没有贴出来。


另外还需要介绍的是SetMediaType函数以及GetMediaType。

GetMediaType即输出pin支持的媒体类型,这里就看transform filter做的是不是很强大,支持多少种输出格式,以下仅仅示例       

HRESULT CXX::GetMediaType(int iPosition, CMediaType * pMediaType)
{
	if(iPosition < 0)
	{
		return E_INVALIDARG;
	}

	if(iPosition >= 2)
	{
		return VFW_S_NO_MORE_ITEMS;
	}
	{
		GUID 	SubType;
		WORD	biBitCount;
		DWORD	biCompression;
		LONG	biWidth = mWidth;

		if(iPosition == 0)
		{
			// IYUV
			SubType = MEDIASUBTYPE_IYUV;
			biCompression = MAKEFOURCC('I', 'Y', 'U', 'V');
			biBitCount = 12;
			biWidth = mWidth;
		}
		else if(iPosition == 1)
		{
			// YV12
			SubType = MEDIASUBTYPE_YV12;
			biCompression = MAKEFOURCC('Y', 'V', '1', '2');
			biBitCount = 12;
			biWidth = mWidth;
		}


		AM_MEDIA_TYPE mt;
		VIDEOINFOHEADER2 vih;

		mt.majortype			= MEDIATYPE_Video;
		mt.subtype				= SubType;
		mOutputFormatId = _subtypeToFormatId(&mt.subtype);

		mt.formattype			= FORMAT_VideoInfo2;
		mt.pbFormat				= (BYTE*)&vih;
		mt.cbFormat				= sizeof(VIDEOINFOHEADER2);

		mt.bFixedSizeSamples	= TRUE;
		mt.bTemporalCompression	= FALSE;
		mt.lSampleSize			= mWidth * mHeight * biBitCount / 8;
		mt.pUnk = NULL;

		VIDEOINFOHEADER2 * pvih2 = (VIDEOINFOHEADER2 *)mt.pbFormat;
		ZeroMemory(pvih2, sizeof(VIDEOINFOHEADER2));



		SetRectEmpty(&(pvih2->rcSource));
		SetRectEmpty(&(pvih2->rcTarget));


		pvih2->bmiHeader.biWidth		= mWidth;
		pvih2->bmiHeader.biHeight		= mHeight;
		mIsTopBottom = pvih2->bmiHeader.biHeight < 0 ? true : false;//对于yuv要转换为rgb时有用,需要知道第一个像素点是在top-down还是bottom-up
		pvih2->bmiHeader.biPlanes		= 1;
		pvih2->bmiHeader.biSize			= sizeof(BITMAPINFOHEADER);
		pvih2->bmiHeader.biBitCount		= biBitCount;
		pvih2->bmiHeader.biCompression	= biCompression;
		pvih2->bmiHeader.biSizeImage	= mWidth * mHeight * biBitCount / 8;

		pvih2->dwCopyProtectFlags = 0;
		pvih2->dwInterlaceFlags = AMINTERLACE_IsInterlaced | AMINTERLACE_DisplayModeBobOnly;
		pvih2->dwPictAspectRatioX = mAspectX * mWidth;
		pvih2->dwPictAspectRatioY = mAspectY * mHeight;
		pvih2->dwControlFlags = 0;
		pvih2->dwReserved2 = 0;

		pMediaType->Set(mt);

	}
	return NOERROR;
}


 

SetMediaType函数,协商完成后设置具体的类型

HRESULT CXX::SetMediaType(PIN_DIRECTION direction, const CMediaType *pMediaType)
{
	LPBITMAPINFOHEADER lpbi = HEADER(pMediaType->Format());
	VIDEOINFOHEADER2 *vinfo2 = NULL;
	MPEG2VIDEOINFO *mpeg2vinfo = NULL;
	BITMAPINFOHEADER *binfo  = NULL;
	long lPitchWidth  = 0;
	long lPitchHeight = 0;

	if(direction == PINDIR_INPUT)
	{
		AM_MEDIA_TYPE mt;
		mt = (AM_MEDIA_TYPE)(*pMediaType);
		if(pMediaType->formattype == FORMAT_MPEG2Video)
		{
			mpeg2vinfo = (MPEG2VIDEOINFO*)pMediaType->pbFormat;
			vinfo2 = &mpeg2vinfo->hdr;
		}

		if(vinfo2)
		{
			mWidth  = vinfo2->rcSource.right;
			mHeight = vinfo2->rcSource.bottom;
			if(mWidth  < 0)
			{
				mWidth *= -1;
			}
			if(mHeight < 0)
			{
				mHeight *= -1;
			}

			mAspectX = vinfo2->dwPictAspectRatioX;//此处为图像拉伸比例
			mAspectY = vinfo2->dwPictAspectRatioY;
		}
	}
	else // (direction == PINDIR_OUTPUT)
	{
		AM_MEDIA_TYPE mt;
		mt = (AM_MEDIA_TYPE)(*pMediaType);

		if(pMediaType->formattype == FORMAT_VideoInfo2)
		{
			vinfo2 = (VIDEOINFOHEADER2*)pMediaType->pbFormat;
			binfo = &vinfo2->bmiHeader;
		}

		if(binfo)
		{
			lPitchWidth  = binfo->biWidth;
			lPitchHeight = binfo->biHeight;
			if(lPitchWidth  < 0)
			{
				lPitchWidth *= -1;
			}
			if(lPitchHeight < 0)
			{
				lPitchHeight *= -1;
			}
		}
		m_lPitchWidth  = lPitchWidth;//实际协商后的宽度,可能和实际图像的宽度不一致,因为需要字节对齐
		m_lPitchHeight = lPitchHeight;

		binfo->biWidth = mWidth;	
		binfo->biHeight = mHeight;
	}

        // Pass the call up to my base class
	HRESULT hr = CTransformFilter::SetMediaType(direction, pMediaType);
	if(SUCCEEDED(hr))
	{
		return NOERROR;
	}
	else
	{
		return hr;
	}
}

 

ffmpeg的初始化及释放,单独贴出,可以放在构造函数、析构函数里面,

	// init ffmpeg
	av_register_all(); 
	
	mpCodec = avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO);  
	if(mpCodec == NULL) 
	{  
		return;  
	} 

	mpCodecCtx = avcodec_alloc_context3(mpCodec);
	if(mpCodecCtx == NULL)
	{
		return;
	}

	if(avcodec_open2(mpCodecCtx, mpCodec, NULL) < 0) 
	{
		return ;
	} 

	mpFrame = avcodec_alloc_frame();  
	if(mpFrame == NULL)  
	{  
		return;  
	} 

	if (mpCodecCtx)
	{
		avcodec_close(mpCodecCtx); 
		av_free(mpCodecCtx);
	}

	if (mpFrame)
	{
		avcodec_free_frame(&mpFrame); 
	}

至于用于filter注册的几个函数就不贴代码了。以上代码仅仅作为参考,应该很少有和我的需求相像的,只是描述在做编解码Transform filter时的大致流程,以及各个虚函数可以用来做什么工作,仅此而已啊。



  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值