DShow代码分析(适合filter有开发经验的人士)

相信大家都用过Windows Media Player,当打开一个文件以后,它会根据文件的类型自动选择合适的解码器,如果找不到合适的解码器,系统会提示说:无法识别的媒体格式。在Directshow的专业术语中,解码器叫做 filter.
最近,笔者开发了一套MPEG4的filter.

开发目标:
(1)用自己的算法实现MPEG4的压缩和解压.
(2)使用开发成功的Filter进行录象,保存为AVI文件.
(3)让Windows Media Player或其他媒体播放器可以播放录的文件
(4)必须保证音频视频的同步.

说明:
(1)录象的时候,图象压缩采用自己开发的这个MPEG4 Encode filter,声音则使用PCM,不压缩。
(2)MPEG4压缩和解压算法包装成lib,以祯为单位直接调用,不在本文讨论范围.


MPEG4 Encode Filter
MPEG4 Decode Filter
这两个filter都继承自CTransformFilter(从CVideoTransformFilter继承也可以,但是目前我没有找到区别所在).

代码摘录:
MPEG4 Encode Filter.

类定义
class CMPEG4Encode
    : public CVideoTransformFilter
{
public:

    DECLARE_IUNKNOWN;
     
    CMPEG4Encode(LPUNKNOWN pUnk, HRESULT *pHr);
    ~CMPEG4Encode();
    static CUnknown *CreateInstance(LPUNKNOWN punk, HRESULT *pHr);

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

    HRESULT CheckInputType(const CMediaType* mtIn) ;
    HRESULT CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut);
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType) ;

    HRESULT DecideBufferSize(IMemAllocator *pAlloc,
                             ALLOCATOR_PROPERTIES *pProperties);

    HRESULT CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin) { return S_OK; }

private:

        bool m_bDllInit;
int m_nVideoWidth;
int m_nVideoHeight;

HRESULT Copy(IMediaSample *pSource, IMediaSample *pDest) ;

BOOL IsRGB24(const CMediaType *pMediaType);

        CCritSec m_EncodeLock;

Encoder m_Context;

};

CheckInputType函数实现:
(只接受RGB24的输入)

HRESULT CMPEG4Encode::CheckInputType(const CMediaType *mtIn)
{
    if (*mtIn->FormatType() != FORMAT_VideoInfo) 
    {
        return E_INVALIDARG;
    }
    if (*mtIn->Subtype() != MEDIASUBTYPE_RGB24) 
    {
        return E_INVALIDARG;
    }
    // 看看格式是否是24位调色板格式RGB24
    if (IsRGB24(mtIn)) 
    {
int nRet = init_coder(m_nVideoWidth,m_nVideoHeight,25,350000,2000,10,20,5,31,1,5,&m_Context);
m_bDllInit = (nRet == 0);

return NOERROR;
    }
    return E_FAIL;

} // CheckInputType

Transform函数实现:
(转换)

HRESULT CMPEG4Encode::Transform(IMediaSample *pIn, IMediaSample *pOut)
{
    //复制(copy函数中有压缩的实现)
    HRESULT hr = Copy(pIn, pOut);
    if (FAILED(hr)) {
        return hr;
    }
} // Transform

Copy函数实现:

HRESULT CMPEG4Encode::Copy(IMediaSample *pSource, IMediaSample *pDest) 
{
     unsigned char *pSourceBuffer, *pDestBuffer;
     unsigned long lSourceSize = pSource->GetActualDataLength();
     unsigned long lDestSize;

     pSource->GetPointer(&pSourceBuffer);
     pDest->GetPointer(&pDestBuffer); 
     int nRet = 0;

     // MPEG4压缩实现!!!!!!压缩后,样本长度写在lDestSize里
     nRet=code(&m_Context,pSourceBuffer,pDestBuffer,&lDestSize);
     
     // 复制样本时间
    REFERENCE_TIME TimeStart, TimeEnd;
    if (NOERROR == pSource->GetTime(&TimeStart, &TimeEnd)) 
    {
        pDest->SetTime(&TimeStart, &TimeEnd);
    }

    // 复制Media Time
    LONGLONG MediaStart, MediaEnd;
    if (pSource->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR) {
        pDest->SetMediaTime(&MediaStart,&MediaEnd);
    }

    // 复制同步点属性

    HRESULT hr = pSource->IsSyncPoint();
    if (hr == S_OK) 
    {
        pDest->SetSyncPoint(nRet);
    }
    else if (hr == S_FALSE) 
    {
        pDest->SetSyncPoint(FALSE);
    }
    else 
    {   
        // 意外错误
        return E_UNEXPECTED;
    }

    // 复制媒体类型

    AM_MEDIA_TYPE *pMediaType;
    pSource->GetMediaType(&pMediaType);
    pDest->SetMediaType(pMediaType);
    DeleteMediaType(pMediaType);

    // 复制preroll属性

    hr = pSource->IsPreroll();
    if (hr == S_OK) 
    {
        pDest->SetPreroll(TRUE);
    }
    else if (hr == S_FALSE) 
    {
        pDest->SetPreroll(FALSE);
    }
    else 
    {
//意外错误
        return E_UNEXPECTED;
    }
    
    // 复制中断点属性

    hr = pSource->IsDiscontinuity();
    if (hr == S_OK) 
    {
pDest->SetDiscontinuity(TRUE);
    }
    else if (hr == S_FALSE) 
    {
        pDest->SetDiscontinuity(FALSE);
    }
    else 
    {
//意外错误
        return E_UNEXPECTED;
    }

    // 设置实际数据长度
    pDest->SetActualDataLength(lDestSize);
    
    return NOERROR;

} // Copy

DecideBufferSize函数实现

HRESULT CMPEG4Encode::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProperties)
{
    if (m_pInput->IsConnected() == FALSE) 
    {
        return E_UNEXPECTED;
    }

    HRESULT hr = NOERROR;

    pProperties->cBuffers = 1;
    pProperties->cbBuffer = (m_pInput->CurrentMediaType().GetSampleSize());

    if (!m_pInput->CurrentMediaType().bFixedSizeSamples) {
if (pProperties->cbBuffer < 100000) {
            pProperties->cbBuffer = 100000;
        }
    }

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

    if (pProperties->cBuffers > Actual.cBuffers ||
            pProperties->cbBuffer > Actual.cbBuffer) {
                return E_FAIL;
    }
    return NOERROR;

} // DecideBufferSize

GetMediaType函数实现

HRESULT CMPEG4Encode::GetMediaType(int iPosition, CMediaType *pMediaType)
{
    if (m_pInput->IsConnected() == FALSE) {
        return E_UNEXPECTED;
    }

    if (iPosition < 0) {
        return E_INVALIDARG;
    }

    if (iPosition > 0) {
        return VFW_S_NO_MORE_ITEMS;
    }

    *pMediaType = m_pInput->CurrentMediaType();


    return NOERROR;

} // GetMediaType

类工厂:

const AMOVIESETUP_MEDIATYPE sudPinTypes =
{&MEDIATYPE_Video       // clsMajorType
, &MEDIASUBTYPE_NULL};  // clsMinorType

const AMOVIESETUP_PIN psudPins[] =
{ { L"Input"            // strName
  , FALSE               // bRendered
  , FALSE               // bOutput
  , FALSE               // bZero
  , FALSE               // bMany
  , &CLSID_MPEG4Encode  // clsConnectsToFilter
  , L""                 // strConnectsToPin
  , 1                   // nTypes
  , &sudPinTypes     // lpTypes
  }
, { L"Output"           // strName
  , FALSE               // bRendered
  , TRUE                // bOutput
  , FALSE               // bZero
  , FALSE               // bMany
  , &CLSID_MPEG4Encode  // clsConnectsToFilter
  , L""                 // strConnectsToPin
  , 1                   // nTypes
  , &sudPinTypes     // lpTypes
  }
};


const AMOVIESETUP_FILTER sudMPEG4Encode =
{ &CLSID_MPEG4Encode    // clsID
, L"IES MPEG4 Encode"       // strName
, MERIT_DO_NOT_USE      // dwMerit
, 2 // nPins
, psudPins };           // lpPin

// CreateInstance机制需要,只能放在后面
CFactoryTemplate g_Templates[]=
    {   { L"IES MPEG4 Encode"
        , &CLSID_MPEG4Encode
        , CMPEG4Encode::CreateInstance
        , NULL
        , &sudMPEG4Encode }
    };
int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);

以上是mpeg4 Encode filter的主要代码框架.

MPEG4 decode filter:

类定义
class CMPEG4Decode
    : public CTransformFilter
{
public:

    DECLARE_IUNKNOWN;
  
    CMPEG4Decode(LPUNKNOWN pUnk, HRESULT *pHr);
    ~CMPEG4Decode();
    static CUnknown *CreateInstance(LPUNKNOWN punk, HRESULT *pHr);

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

    HRESULT CheckInputType(const CMediaType* mtIn) ;
    HRESULT CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut);
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType) ;

    HRESULT DecideBufferSize(IMemAllocator *pAlloc,
                             ALLOCATOR_PROPERTIES *pProperties);

    HRESULT CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin) 
    { 
       return S_OK; 
    }
private:

    HRESULT Copy(IMediaSample *pSource, IMediaSample *pDest) ;

BOOL IsRGB24(const CMediaType *pMediaType);

bool m_bInit;
int m_nVideoWidth;
int m_nVideoHeight;

        CCritSec m_EncodeLock;

DECODER * m_pDeocder;

BOOL Converse(BYTE * pSource);
};

CheckInputType实现

HRESULT CMPEG4Decode::CheckInputType(const CMediaType *mtIn)
{
    if (*mtIn->FormatType() != FORMAT_VideoInfo) {
        return E_INVALIDARG;
    }
    
    init_codec();
    init_decoder(m_nVideoWidth,m_nVideoHeight,&m_pDeocder);
    m_bInit = true;

    return NOERROR;

} // CheckInputType

Transform实现和Copy函数与Encode filter的一致,知识copy里的处理不同,一个是encode,一个是decode.

DecideBufferSize实现:

HRESULT CMPEG4Decode::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProperties)
{
    if (m_pInput->IsConnected() == FALSE) 
    {
        return E_UNEXPECTED;
    }

    HRESULT hr = NOERROR;

    pProperties->cBuffers = 1;
    //注意,由于解压后数据长度增加,所以采用(m_pInput->CurrentMediaType().GetSampleSize())是不够的
    pProperties->cbBuffer = m_nVideoWidth * m_nVideoHeight *3;

    if (!m_pInput->CurrentMediaType().bFixedSizeSamples) {
if (pProperties->cbBuffer < 100000) {
            pProperties->cbBuffer = 100000;
        }
    }

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

    if (pProperties->cBuffers > Actual.cBuffers ||
            pProperties->cbBuffer > Actual.cbBuffer) {
                return E_FAIL;
    }
    return NOERROR;

} // DecideBufferSize

GetMediaType函数实现

HRESULT CMPEG4Decode::GetMediaType(int iPosition, CMediaType *pMediaType)
{
    if (m_pInput->IsConnected() == FALSE) {
        return E_UNEXPECTED;
    }

    if (iPosition < 0) {
        return E_INVALIDARG;
    }

    if (iPosition > 0) {
        return VFW_S_NO_MORE_ITEMS;
    }

    *pMediaType = m_pInput->CurrentMediaType();


    return NOERROR;

} // GetMediaType

类工厂:

const AMOVIESETUP_MEDIATYPE sudPinTypes =
{
    &MEDIATYPE_Video,       // Major type
    &MEDIASUBTYPE_NULL      // Minor type
};

const AMOVIESETUP_PIN psudPins[] =
{ { L"Input"            // strName
  , FALSE               // bRendered
  , FALSE               // bOutput
  , FALSE               // bZero
  , FALSE               // bMany
  , &CLSID_MPEG4Decode  // clsConnectsToFilter
  , NULL                 // strConnectsToPin
  , 1                   // nTypes
  , &sudPinTypes     // lpTypes
  }
, { L"Output"           // strName
  , FALSE               // bRendered
  , TRUE                // bOutput
  , FALSE               // bZero
  , FALSE               // bMany
  , &CLSID_MPEG4Decode  // clsConnectsToFilter
  , NULL                 // strConnectsToPin
  , 1                   // nTypes
  , &sudPinTypes     // lpTypes
  }
};


const AMOVIESETUP_FILTER sudMPEG4Decode =
{ &CLSID_MPEG4Decode    // clsID
, L"IES MPEG4 Decode"       // strName
, MERIT_DO_NOT_USE      // dwMerit
, 2 // nPins
, psudPins };           // lpPin

// CreateInstance机制需要,只能放在后面
CFactoryTemplate g_Templates[]=
    {   { L"IES MPEG4 Decode"
        , &CLSID_MPEG4Decode
        , CMPEG4Decode::CreateInstance
        , NULL
        , &sudMPEG4Decode }
    };
int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);

以上摘录了两个filter的主要框架代码.
目前,我这两个filter编译成功,开始测试,下面是测试的一些现象和问题.

测试环境:
Windows XP 简体中文版
DirectX 8.1
测试工具:
GraphEdt

1.压缩解压实验
  
  Graph连接图:
  
  Logitech QuickCam Pro 4000 --> My MPEG4 Encode Filter --> My MPEG4 Decode Filter -->VMR Video Render
  
  注意:实际在连接My MPEG4 Decode Filter和VMR Video Render时,中间还会自动加入一个Color Space Convert
  
  测试结果:图象清晰,剧烈运动时有些许叠影,但几乎可以忽略不计。
            图象是倒的!!!
  
  结论和改进措施:
            这个实验基本证明我们这两个Filter基本框架无误,工作正常。
            图象是倒的,怎么办呢?
            由于图象的存储是线形的,一个象素三个字节.后面紧跟下一个象素的三个字节。这样看来,只要简单倒一下就可以了.
            
   BOOL CMPEG4Decode::Converse(BYTE * pSource)
{
const PIX = m_nVideoWidth*m_nVideoHeight;
const PIXS = PIX*3;
BYTE * pTemp;
pTemp = (BYTE *)malloc(PIXS);
for(int i=1;i<=PIX;i++)
{
memcpy(pTemp+(i-1)*3,pSource+PIXS-i*3,3);
}
memcpy(pSource,pTemp,PIXS);
free(pTemp);
return TRUE;
}
用Converse函数对上面描述的decode filter代码里面的Copy函数进行改进,问题解决,一切ok,happy!!!!。

2.录象实验
  Graph连接图:
  
  Logitech QuickCam Pro 4000 --> My MPEG4 Encode Filter --> AVI Mux --> FileWrite(Mytest.avi)
  
  测试结果:一切正常,按需要生成了AVI文件.
  
  如果你现在用Media Player播放该AVI文件,屏幕上会出现一片雪花和条纹,最后程序还会崩溃,faint!
  
  不过这样的结果应该在你的意料之中.

3.打开刚才录的AVI,看究竟发生了什么事情!
  
  在GraphEdt里面选择对 Mytest.avi进行Renderfile,会出现以下的Graph:
  这个步骤也就是Windows Media Player播放文件的步骤!!!
  
  File Source(Mytest.avi) --> AVI Splitter --> Color Space Convert --> VMR Video Render
  
  前面的 File Source(Mytest.avi) --> AVI Splitter是我们所希望的,AVI Splitter会把我们Encode的视频分离出来,这很好。
  但是接下来就不对了.在还没有用My Decode Filter解码之前,系统就直接连接到VMR Video Render了,Color Space Convert 只是中间的一个色彩空间的转换。
  那么,究竟如何才能让系统认识我们分离出来的流呢,并为我们正确加上My decode Filter呢?
  老实说,我现在还不知道。
  我写这篇文章的目的是抛砖引玉,希望各位一起出谋划策,得以共同进步。
 
4.手工添加My decode Filter.
  上面的实验很令我沮丧和迷惑。为此我做了很多工作。
   手工建立下面的Graph连接图:
     
  File Source(Mytest.avi) --> AVI Splitter -->My MPEG4 Decode Filter -->Color Space Convert --> VMR Video Render
  
  现在一切又正常了。
  现在问题是:如何让系统自动完成这一切。
  
我要写的就写完了,真切希望看到我这篇文章并对DirectShow有兴趣的高人能够就这些问题给些意见,同时,希望大家:
 
 
 “知无不言,言无不尽;言者无罪,闻者足戒...”
 
 有一位高人,名唤陆其明,写过很多DirectShow方面的启蒙文章,现在从事这个领域的人大都知道他。该同志现在在写一本书,叫做《DirectShow宝典》,据说已经完成。我曾将这些问题在其个人主页上多次提出,但该位仁兄除了不痛不痒说几句以外,基本上没有作用,可能是我太笨的缘故。

你可能感兴趣的:(资料集)