您的位置:首页 > 其它

Directshow使用ffmpeg构建解码filter

2014-06-14 10:40 411 查看
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时的大致流程,以及各个虚函数可以用来做什么工作,仅此而已啊。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: