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,如下:
那就开始一个个贴代码吧,顺带再说一句CTransformFilter已经帮我们实现了两个pin,一个输入pin,一个输出pin,在其源码的定义里很清楚。
1. CheckInputType,检查输入格式
很简单,判断输入类型是否是mpeg2-video。
2.CheckTransform,检查输入类型CheckInputType,并且检查输出类型,这里指定为I420或YV12
3. DecideBufferSize,设置buffer的大小及数目,并验证设置是否生效
4.Transform,实际工作的地方,把输入的码流通过ffmpeg转换为yuv图像,这段代码有点多啊
上面的格式转换,以及宽度拉伸的函数由于是第三方库,因此没有贴出来。
另外还需要介绍的是SetMediaType函数以及GetMediaType。
GetMediaType即输出pin支持的媒体类型,这里就看transform filter做的是不是很强大,支持多少种输出格式,以下仅仅示例
SetMediaType函数,协商完成后设置具体的类型
ffmpeg的初始化及释放,单独贴出,可以放在构造函数、析构函数里面,
至于用于filter注册的几个函数就不贴代码了。以上代码仅仅作为参考,应该很少有和我的需求相像的,只是描述在做编解码Transform filter时的大致流程,以及各个虚函数可以用来做什么工作,仅此而已啊。
我做的这个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时的大致流程,以及各个虚函数可以用来做什么工作,仅此而已啊。
相关文章推荐
- Directshow使用ffmpeg构建解码filter
- directshow 与 ffmpeg 联合使用时filter注册不成功的问题
- 如何使用netfilter/iptables构建防火墙
- 在vs2008下使用ffmpeg(3):结构体构建
- directshow使用自定义的filter(多输入,单输出)出现cpu使用率过高的问题,filter的丢帧处理
- 使用ffmpeg dxva技术解码
- 【视频编解码】Linux下FFmpeg编译安装及其ffplay的安装使用
- 如何使用netfilter/iptables构建防火墙
- JBoss 系列十九:使用JGroups构建块RspFilter对群组通信返回消息进行过滤
- 通过C++/CLI使用FFMPEG库进行视频解码[初步]
- Mina中使用多个filter解码时问题
- 如何使用netfilter/iptables构建防火墙
- 如何使用netfilter/iptables构建防火墙
- 在window下使用ffmpeg进行解码
- 如何使用netfilter/iptables构建Linux防火墙
- 如何使用filter构建iptables防火墙
- 关于使用DirectShow架构,传输YUV420数据的Filter与Video Renderer Filter连接问题的解决办法。
- directshow 不注册使用filter
- 在window下使用ffmpeg进行解码
- Node.js高级编程:用Javascript构建可伸缩应用(4)2.4 核心API基础-使用Buffer处理,编码,解码二进制数据