做了一个directshow的filter,把RGB视频流变成黑白的

做的这个directshow的filter属于transform filter。在其间,参考了,directshow的帮助文档,一本外文的介绍directshow的书(这本书不错,里面的代码,虽然感觉有copy directshow帮助文档的成分,但是,讲的挺不错),还有vc知识库中的两篇文章了,这两篇文章也写得挺好的。如果当初我没有看,我想会走不少弯路。

要写directshow的transform filter 已经准备了好久了。曾经用过directshow,不过里面的也就是用现成的filter组成一个filter graph,然后run,就可以了。把自己写filter看作挺难的事情。现在发现,其实写transform filter不难,当然,也只是像我这样简单的。但是,处理的速度确实是问题。

首先,directshow filter是符合com组件规范的,也就是,其是一个com组件,要符合com的规范,需要实现一些函数的,不过,已经有不少的基类了,只需要继承就可以了,这样,对com组件所涉及的知识就少了很多。在编写directshow filter时,只要找准了要继承的基类,然后,实现里面的虚函数等等,写上自己的控制代码,就可以了。
下面先简单的给出一些源代码和说明,具体的解释,以后会做。

这个简单的transform filter由三个文件组成:

1。ToGrayFilter.def:由于filter是个基于dll的com组件,所以一般的filter要实现几个入口函数。要导出dll中的函数有两种方法:一种是在定义函数时使用导出关键字__declspec(dllexport),也就是在.h文件中定义函数如下:

extern “C“  __declspec(dllexport)  BOOL   DllRegisterServer;等等;第二种方法是使用模块定义文件,这也是我在这里用的方法。

//ToGrayFilter.def-----------------------------------------------------------------------

//

LIBRARY ToGrayFilter.ax

EXPORTS
   DllMain             PRIVATE    //dll的入口函数,directshow中实现的是dllEntryPoint
   DllGetClassObject    PRIVATE //用于获得类工厂指针
   DllCanUnloadNow       PRIVATE //系统空闲时会调用这个函数,确定是否可以卸载DLL
   DllRegisterServer     PRIVATE //将com组件注册到注册表中
   DllUnregisterServer    PRIVATE  //删除注册表中的com组件的注册信息

上面的函数是作为一个典型的自注册com组件dll所必需的5个导出函数。

2。ToGrayFilter.h

在这个文件中声明了一个c++类,这个类是从directshow中的一个用于方便用户编写filter的基类中继承的。

//ToGrayFilter.h-------------------------------------------------------------------------------------------------------
//
#ifndef TOGRAYFILTER_H_
#define TOGRAYFILTER_H_

//
// {5F2265B1-A841-4eb7-871F-5556436042AC}
DEFINE_GUID(CLSID_ToGrayFilter,
0x5f2265b1, 0xa841, 0x4eb7, 0x87, 0x1f, 0x55, 0x56, 0x43, 0x60, 0x42, 0xac);


class CToGrayFilter:public CTransformFilter
{
public: 
 CToGrayFilter(TCHAR *pName,LPUNKNOWN pUnk,HRESULT *phr);  //constructor
 ~CToGrayFilter();  //destructor

public:
    // Static object-creation method (for the class factory) //必须有的
 //
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr);

public:
 //implement the base filter 's method,下面5个函数是CTranseformFilter的虚函数,必须实现了的
 //
 HRESULT CheckInputType(const CMediaType *pmtIn);
 HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
 HRESULT CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut);
 HRESULT DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProp);
 HRESULT Transform(IMediaSample *pSource,IMediaSample *pDest);

 //this method is also in the base filter class,but in the base class it does noting,
 //implement here just want to get  m_VihIn and m_VihOut
 //
 HRESULT SetMediaType(PIN_DIRECTION direction, const CMediaType *pmt);


private:
 //my own process method,这是我自己的处理函数:),在Transform这个函数中调用的
 //
 HRESULT ToGray(BYTE *pbInput,BYTE *pbOutput);
 
 //a help method ,just copy from helper document,这个函数,最终没有在这个filter中用,因为不知道为何,
 //感觉有些问题,按理说是不应该这样的。最后是自己直接操作了
 void GetVideoInfoParameters(
    const VIDEOINFOHEADER *pvih, // Pointer to the format header.
    BYTE  * const pbData,   // Pointer to the first address in the buffer.
    bool bYuv,      // Is this a YUV format? (true = YUV, false = RGB)
    DWORD *pdwWidth,        // Returns the width in pixels.
    DWORD *pdwHeight,       // Returns the height in pixels.
    LONG  *plStrideInBytes, // Add this to a row to get the new row down.
    BYTE **ppbTop          // Returns a pointer to the first byte in the
                            // top row of pixels.
     );

 VIDEOINFOHEADER m_VihIn;   // Holds the current video format (input),可以把每一帧图像当作bmp位图
    VIDEOINFOHEADER m_VihOut;  // Holds the current video format (output)

 //the imformation about every picture,put here just for speed
 //
 DWORD m_bytePerLine;//the real numbers of bits in one line,just for handy
 int m_Width;//the width of the bitmap,to use it ,just handy
 int m_Height;//the height of the bitmap
};
#endif


3。ToGrayFilter.cpp

// ToGrayFilter.cpp : 定义 DLL 应用程序的入口点。
//

#include "stdafx.h"

//the include file for directshow filter
//
#include "streams.h" //用到了filter的基类,就要包含这个头文件的
#include <initguid.h>  //
#include <tchar.h>
#include <stdio.h>

#include "ToGrayFilter.h"

#pragma warning(disable:4715)

//--------------------------------------------------------------------

CToGrayFilter:: CToGrayFilter(TCHAR *pName,LPUNKNOWN pUnk,HRESULT *phr):
    CTransformFilter(pName, pUnk, CLSID_ToGrayFilter)

}

CToGrayFilter::~CToGrayFilter()
{
}

//------------------------------------------------------------------------------
//
//this method check this filter's input pin could receive which kind of media type
//
HRESULT CToGrayFilter::CheckInputType(const CMediaType *pmtIn)
{
 if ((pmtIn->majortype != MEDIATYPE_Video) ||
        (pmtIn->subtype != MEDIASUBTYPE_RGB24) ||
        (pmtIn->formattype != FORMAT_VideoInfo) ||
        (pmtIn->cbFormat < sizeof(VIDEOINFOHEADER)))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

 VIDEOINFOHEADER *pVih =
        reinterpret_cast<VIDEOINFOHEADER*>(pmtIn->pbFormat);


    // Everything is good.
    return S_OK;

}

//the downstream filter check this filter's output pin ,then the method will be used
//now because the media type has not been changed ,so only return the media type of
//this filter's input pin
//
HRESULT CToGrayFilter::GetMediaType(int iPosition, CMediaType *pMediaType)
{
    // The output pin calls this method only if the input pin is connected.
    ASSERT(m_pInput->IsConnected());

    // There is only one output type that we want, which is the input type.
    if (iPosition < 0)
    {
        return E_INVALIDARG;
    }
    else if (iPosition == 0)
    {
  //this maybe OK now
        return m_pInput->ConnectionMediaType(pMediaType);
    }
    return VFW_S_NO_MORE_ITEMS;

}


//this method checks if a proposed output type is compatible with the current input type.
//The method is also called if the input pin reconnects after the output pin connects.
//
HRESULT CToGrayFilter::CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut)
{
 // Check the major type.
 //
    if (mtOut->majortype != MEDIATYPE_Video)
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    // Check the subtype and format type.
 //

 // Make sure the subtypes match
 //
    if (mtIn->subtype != mtOut->subtype)
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
 if ((mtOut->formattype != FORMAT_VideoInfo) ||
        (mtOut->cbFormat < sizeof(VIDEOINFOHEADER)))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

 // Compare the bitmap information against the input type.
 //
    ASSERT(mtIn->formattype == FORMAT_VideoInfo);
    BITMAPINFOHEADER *pBmiOut = HEADER(mtOut->pbFormat);
    BITMAPINFOHEADER *pBmiIn = HEADER(mtIn->pbFormat);

 if ((pBmiOut->biWidth <= pBmiIn->biWidth) &&
        (pBmiOut->biHeight == abs(pBmiIn->biHeight)))
    {
       return S_OK;
    }
    return VFW_E_TYPE_NOT_ACCEPTED;

}


//this method is used during the output pin connection process.
//the output pin is responsible for negotiating the allocation of data stream buffers
//during the pin connection process,
//even if this allocation is actually done by the input pin of the downstream filter.
//
HRESULT CToGrayFilter::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProp)
{
 // Make sure the input pin is connected.
 //
    if (!m_pInput->IsConnected())
    {
        return E_UNEXPECTED;
    }

 // Our strategy here is to use the upstream allocator as the guideline,
    // but also defer to the downstream filter's request
    // when it's compatible with us.

 // First, find the upstream allocator...
    ALLOCATOR_PROPERTIES InputProps;

    IMemAllocator *pAllocInput = 0;
    HRESULT hr = m_pInput->GetAllocator(&pAllocInput);

    if (FAILED(hr))
    {
        return hr;
    }

 // ...now get the properties.
    hr = pAllocInput->GetProperties(&InputProps);
    pAllocInput->Release();

    if (FAILED(hr))
    {
        return hr;
    }

 // Buffer alignment should be non-zero [zero alignment makes no sense!].
    if (pProp->cbAlign == 0)
    {
        pProp->cbAlign = 1;
    }

    // Number of buffers must be non-zero.
    if (pProp->cbBuffer == 0)
    {
        pProp->cBuffers = 1;
    }

 // For buffer size, find the maximum of the upstream size and
    // the downstream filter's request.
    pProp->cbBuffer = max(InputProps.cbBuffer, pProp->cbBuffer);

    // Now set the properties on the allocator that was given to us.
    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProp, &Actual);
    if (FAILED(hr))
    {
        return hr;
    }

}

HRESULT CToGrayFilter::Transform(IMediaSample *pSource,IMediaSample *pDest)
{
 // Get pointers to the underlying buffers.
 //
    BYTE *pBufferIn, *pBufferOut;
 HRESULT hr;
    hr = pSource->GetPointer(&pBufferIn);
    if (FAILED(hr))
    {
        return hr;
    }
    hr = pDest->GetPointer(&pBufferOut);
    if (FAILED(hr))
    {
        return hr;
    }

 // Process the data.
 //
 ToGray(pBufferIn,pBufferOut);

}

//------------------------------------------------------------------------------
//this method is also in the base filter class,but in the base class it does noting,
//implement here just want to get  m_VihIn and m_VihOut
//

HRESULT CToGrayFilter::SetMediaType(PIN_DIRECTION direction,
                               const CMediaType *pmt)
{
    if (direction == PINDIR_INPUT)
    {
        ASSERT(pmt->formattype == FORMAT_VideoInfo);
        VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)pmt->pbFormat;

        // WARNING! In general you cannot just copy a VIDEOINFOHEADER
        // struct, because the BITMAPINFOHEADER member may be followed by
        // random amounts of palette entries or color masks. (See VIDEOINFO
        // structure in the DShow SDK docs.) Here it's OK because we just
        // want the information that's in the VIDEOINFOHEADER struct itself.

        CopyMemory(&m_VihIn, pVih, sizeof(VIDEOINFOHEADER));

    }
    else   // Output pin
    {
        ASSERT(direction == PINDIR_OUTPUT);
        ASSERT(pmt->formattype == FORMAT_VideoInfo);
        VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)pmt->pbFormat;

        CopyMemory(&m_VihOut, pVih, sizeof(VIDEOINFOHEADER));
    }

 //------------------------------------------------
 //
 //   DWORD m_bytePerLine;//the real numbers of bits in one line,just for handy
 //int m_Width;//the width of the bitmap,to use it ,just handy
 //int m_Height;//the height of the bitmap
 m_Width=m_VihIn.bmiHeader.biWidth;
 m_Height=m_VihIn.bmiHeader.biHeight;
 m_bytePerLine=(24*m_Width+31)/32*4;


    return S_OK;
}

//----------------------------------------------------------------------------------
//my own process method ,to change the data to gray
//
HRESULT CToGrayFilter::ToGray(BYTE *pbInput,BYTE *pbOutput)
{
 DWORD dwWidth, dwHeight;       // Width and height in pixels (input)
    DWORD dwWidthOut, dwHeightOut; // Width and height in pixels (output)
    LONG  lStrideIn, lStrideOut;   // Stride in bytes
    BYTE  *pbSource, *pbTarget;    // First byte first row, source & target

   
//下面这一段都没有用了,因为感觉这个函数比较古怪,最后是用自己的处理bmp位图的方法得到数据的

//GetVideoInfoParameters(&m_VihIn, pbInput,false, &dwWidth, &dwHeight, 
 //      &lStrideIn, &pbSource);
  // GetVideoInfoParameters(&m_VihOut, pbOutput,false, &dwWidthOut, &dwHeightOut, 
 //      &lStrideOut, &pbTarget);

 // Formats should match (except maybe stride).
   // ASSERT(dwWidth == dwWidthOut);
    //ASSERT(abs(dwHeight) == abs(dwHeightOut));

 //here to process the data
 //
 /*for (DWORD y = 0; y < dwHeight; y++)
    {
        WORD *pwTarget = (WORD*)pbTarget;
        WORD *pwSource = (WORD*)pbSource;

//下面是对RGB32的处理,所以,对于RGB24如果这样做,会有问题的

  //RGBQUAD *pPixelTarget = (RGBQUAD*)pbTarget;
  //RGBQUAD *pPixelSource = (RGBQUAD*)pbSource;
  //      for (DWORD x = 0; x < dwWidth; x++)
  //      {
  // BYTE grayColor;
  // grayColor=
  //  (pPixelSource[x].rgbBlue+pPixelSource[x].rgbGreen+pPixelSource[x].rgbRed)/3;
  //          // pPixelTarget[x] is the x'th pixel in the row.
  //          pPixelTarget[x].rgbBlue = pPixelSource[x].rgbBlue;
  //          pPixelTarget[x].rgbGreen = pPixelSource[x].rgbGreen;
  //          pPixelTarget[x].rgbRed = pPixelSource[x].rgbRed;
  //          pPixelTarget[x].rgbReserved = 0;
  //      }
  
  
    }*/

 //want to get the imformation for myself
 //

//也可以在这里,当每一帧图像来了的时候,都重新计算偏移等等,但是没有必要了,而且会影响速度
 //DWORD m_bytePerLine;//the real numbers of bits in one line,just for handy
 //int m_Width;//the width of the bitmap,to use it ,just handy
 //int m_Height;//the height of the bitmap
 //m_Width=m_VihIn.bmiHeader.biWidth;
 //m_Height=m_VihIn.bmiHeader.biHeight;
 //m_bytePerLine=(24*m_Width+31)/32*4;

 BYTE rColor,gColor,bColor,changeColor;
 for (int i=0;i<m_Height;i++)
 {
  for (int j=0;j<m_Width;j++)
  {
   rColor=*(pbInput+(m_Height-i-1)*m_bytePerLine+j*3);
   gColor=*(pbInput+(m_Height-i-1)*m_bytePerLine+j*3+1);
   bColor=*(pbInput+(m_Height-i-1)*m_bytePerLine+j*3+2);

   changeColor=(rColor+gColor+bColor)/3;

   *(pbOutput+(m_Height-i-1)*m_bytePerLine+j*3)=changeColor;
   *(pbOutput+(m_Height-i-1)*m_bytePerLine+j*3+1)=changeColor;
   *(pbOutput+(m_Height-i-1)*m_bytePerLine+j*3+2)=changeColor;
  }
 }

 return S_OK;

}

//下面这个函数,是从帮助文档中copy的函数,不过感觉没有什么用,实际上在我写的这个filter中也没有用 上

//----------------------------------------------------------------------------
//a helper function ,not write by me,just copy from the help docoment
//
void CToGrayFilter::GetVideoInfoParameters(
    const VIDEOINFOHEADER *pvih, // Pointer to the format header.
    BYTE  * const pbData,   // Pointer to the first address in the buffer.
    bool bYuv,      // Is this a YUV format? (true = YUV, false = RGB)
    DWORD *pdwWidth,        // Returns the width in pixels.
    DWORD *pdwHeight,       // Returns the height in pixels.
    LONG  *plStrideInBytes, // Add this to a row to get the new row down.
    BYTE **ppbTop          // Returns a pointer to the first byte in the
                            // top row of pixels.
    )
{
    LONG lStride;

    //  For 'normal' formats, biWidth is in pixels.
    //  Expand to bytes and round up to a multiple of 4.
    if ((pvih->bmiHeader.biBitCount != 0) &&
        (0 == (7 & pvih->bmiHeader.biBitCount)))
    {
        lStride = (pvih->bmiHeader.biWidth * (pvih->bmiHeader.biBitCount / 8) + 3) & ~3;
    }
    else   // Otherwise, biWidth is in bytes.
    {
        lStride = pvih->bmiHeader.biWidth;
    }

    //  If rcTarget is empty, use the whole image.
    if (IsRectEmpty(&pvih->rcTarget))
    {
        *pdwWidth = (DWORD)pvih->bmiHeader.biWidth;
        *pdwHeight = (DWORD)(abs(pvih->bmiHeader.biHeight));
       
        if (pvih->bmiHeader.biHeight < 0 || bYuv)   // Top-down bitmap.
        {
            *plStrideInBytes = lStride; // Stride goes "down".
            *ppbTop           = pbData; // Top row is first.
        }
        else        // Bottom-up bitmap.
        {
            *plStrideInBytes = -lStride;    // Stride goes "up".
            // Bottom row is first.
            *ppbTop = pbData + lStride * (*pdwHeight - 1); 
        }
    }
    else   // rcTarget is NOT empty. Use a sub-rectangle in the image.
    {
        *pdwWidth = (DWORD)(pvih->rcTarget.right - pvih->rcTarget.left);
        *pdwHeight = (DWORD)(pvih->rcTarget.bottom - pvih->rcTarget.top);
       
        if (pvih->bmiHeader.biHeight < 0 || bYuv)   // Top-down bitmap.
        {
            // Same stride as above, but first pixel is modified down
            // and over by the target rectangle.
            *plStrideInBytes = lStride;    
            *ppbTop = pbData +
                     lStride * pvih->rcTarget.top +
                     (pvih->bmiHeader.biBitCount * pvih->rcTarget.left) / 8;
        }
        else  // Bottom-up bitmap.
        {
            *plStrideInBytes = -lStride;
            *ppbTop = pbData +
                     lStride * (pvih->bmiHeader.biHeight - pvih->rcTarget.top - 1) +
                     (pvih->bmiHeader.biBitCount * pvih->rcTarget.left) / 8;
        }
    }
}

 

//-----------------------------------------------------------------------------------------------
//
CUnknown* WINAPI CToGrayFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)

    CToGrayFilter *pFilter = new CToGrayFilter(NAME("To Gray Filter"), pUnk, pHr);
    if (pFilter== NULL)
    {
        *pHr = E_OUTOFMEMORY;
    }
    return pFilter;
}


//the following three method maybe the same for most of the filters
//

extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE ,DWORD,LPVOID );

BOOL APIENTRY DllMain(HANDLE hModule,DWORD dwRes,LPVOID pv)
{
 return DllEntryPoint((HINSTANCE)hModule,dwRes,pv);
}

HRESULT WINAPI DllRegisterServer()
{
 return AMovieDllRegisterServer2(TRUE);
}
HRESULT WINAPI DllUnregisterServer()
{
 return AMovieDllRegisterServer2(FALSE);
}

//---------------------------------------------------
//
AMOVIESETUP_FILTER FilterInfo =
{
    &CLSID_ToGrayFilter,     // CLSID
    L"To Gray Filter",          // Name
    MERIT_DO_NOT_USE,   // Merit
    0,                  // Number of AMOVIESETUP_PIN structs
    NULL                // Pin registration information
};


CFactoryTemplate g_Templates[]={ 
    {
  L"To Gray Filter",
     &CLSID_ToGrayFilter,
     CToGrayFilter::CreateInstance,
     0,
     &FilterInfo,
 }

};
int g_cTemplates=sizeof(g_Templates)/sizeof(g_Templates[0]);
 

上面就是我的程序的所有源代码。这只是一个示例的程序而已,里面有好多东西,其实将来写从CTransformFilter继承的transform filter是完全可以重复使用的,也就是里面的很多东西都差不多都会是这样。代码简单,真正自己的实现只是一个函数而已,里面只是得到像素的值,然后进行转换。也就是说,transformfilter有一个架子,不同的只是对像素的操作而已。

现在匆匆收场了,其实还有好多没有提到的地方,但是现在心情突然不好了,以后再补充吧。

你可能感兴趣的:(properties,filter,input,byte,output,winapi)