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;
}
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; idata[0] + i * mpFrame->linesize[0], width);
a+=width;
}
for (i=0; idata[2] + i * mpFrame->linesize[2], width/2);/* 这边要说明下,我的mpeg2-video解码后的图像在mpFrmae->data里uv反了,因此下面拷贝数据的地方先是data[2]再是data[1],最终的结果是拼接成一帧I420数据*/
a+=width/2;
}
for (i=0; idata[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;
}
}
// 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);
}