4

I am using third party API which I get streams from a callback function

int OnNewImage(BYTE *pData, int nLen)

When I run a simple C++ sample program from console

int continue = 1;

int OnNewImage(BYTE *pData, int nLen)
{
  std::cout << "On new image is called" << std::endl;
  return continue;
}


int main()
{

  // This will block
  int result = DownloadStream(/*params*/...,OnNewImage /*callbackfunction*/);

  return 0;

}

I get no memory leaks.[ memory does not increase ]

But When I use this callback function in a directshow filter, it produce memory leaks.[ memory increase regularly]

What may cause this? And how can I fix it? Any ideas?

UPDATE: My DirectShow Filter structure

What I do:

Basically

  • I get streams at "unsigned __stdcall DVRStreamThread(LPVOID pvParam)" function which call back OnNewImage
  • Then i insert frames into my queue inside that callback[OnNewImage]
  • Finally At FillBuffer I consume frames from queue.

It is am h264 stream. I can able to set simple graph like this

MySourceFilter ---> H264 Decoder ---> Video Renderer

Here is my FilterSourceCode:

Well I have a simple queue which i insert incoming frames then consume: SynchronisedQueue

template <typename T>

  class SynchronisedQueue
        {
        public:

        void Enqueue(const T& data)
        {
        boost::unique_lock<boost::mutex> lock(queueMutex);
        dataQueue.push(data);
        conditionVariable.notify_one();
    } 

    T Dequeue()
    {
        boost::unique_lock<boost::mutex> lock(queueMutex);

        while (dataQueue.size()==0) 
        {
            conditionVariable.wait(lock);
        }

        T result=dataQueue.front(); dataQueue.pop();
        return result;
    } 

    int Size()
    {
        boost::unique_lock<boost::mutex> lock(queueMutex);
        int size = dataQueue.size();
        return size;
    } 

private:

    std::queue<T> dataQueue; 
    boost::mutex queueMutex; 
    boost::condition_variable conditionVariable;  

};

Then My Filter:

DvrSourceFilter [ header]

#define DVRSourceFilterName L"DVRDirectShowFilter"

#include <streams.h>
#include <process.h>
#include <MyDvrApi.h>
#include "SynchronisedQueue.h"



// {F89A85DA-F77C-4d44-893B-CCA43A49E7EF}
DEFINE_GUID(CLSID_DVRSourceFilter, 
0xf89a85da, 0xf77c, 0x4d44, 0x89, 0x3b, 0xcc, 0xa4, 0x3a, 0x49, 0xe7, 0xef);

class DECLSPEC_UUID("34363248-0000-0010-8000-00AA00389B71") Subtype_H264;

class DVRSourceFilter;


using namespace std;


/*
 * **********************
 * DVRPin
 * **********************
 */

class DVRPin : public CSourceStream
{
public: 

    DVRPin(HRESULT *phr, DVRSourceFilter *pFilter);
    ~DVRPin();

    // Override the version that offers exactly one media type
    HRESULT GetMediaType(CMediaType *pMediaType);
    HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest);
    HRESULT FillBuffer(IMediaSample *pSample);

    static int OnNewImage(void *pUser, BYTE *pData, int nLen, int nCh, int tMts, int nType, void *returnHandle);

    // Setters 
    void SetDvrIp(char* dvrIp);
    void SetDvrPort( int dvrPort);
    void SetDvrUserName( char * userName);
    void SetDvrPassword(char* password);
    void SetStartTime(int startTime);
    void SetMilliSecond(int milliSecond);
    void SetChannelNumber(int channelNumber);
    void SetSize(int width, int height);

    // Getters
    char* GetDvrIp();
    int   GetDvrPort();
    char* GetDvrUserName();
    char* GetDvrPassword();
    int   GetStartTime();
    int   GetMilliSecond();
    int   GetChannelNumber();
    int   GetMode();

public: 


    char* dvrIp;
    int dvrPort;
    char* userName;
    char* password;
    int startTime;
    int milliSecond;
    int channelNumber;


    BITMAPINFOHEADER m_bmpInfo;
    BYTE* m_RGB24Buffer;
    DWORD m_RGB24BufferSize;
    bool streamCompleted;
    int hDecHandle;

    HANDLE m_hDVRStreamThreadHandle;
    unsigned int m_dwThreadID;

    SynchronisedQueue<std::vector<BYTE>> IncomingFramesQueue;

protected: 

    virtual HRESULT OnThreadCreate();
    virtual HRESULT OnThreadDestroy();
    virtual HRESULT DoBufferProcessingLoop();

};

/*
 * **********************
 * DVRSourceFilter
 * *********************
 *
 */

class DVRSourceFilter : public CSource
{

public: 

    DECLARE_IUNKNOWN;

    static CUnknown * WINAPI CreateInstance(IUnknown *pUnk, HRESULT *phr);  
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    void SetDVRLiveParameters(char* dvrIP, int dvrPort, char* userName, char* password, int channelNumber, int width, int height); 

private: 
    DVRSourceFilter(IUnknown *pUnk, HRESULT *phr);
    ~DVRSourceFilter();

private: 

    DVRPin *m_pPin;
};

DvrSourceFilter [implementation]

#include "DvrSourceFilter.h"


unsigned __stdcall DVRStreamThread(LPVOID pvParam)
{

    DVRPin* streamReader = (DVRPin*)pvParam;

    int channelBits = 1 << (streamReader->channelNumber - 1);
    streamReader->m_RGB24BufferSize = streamReader->m_bmpInfo.biWidth * streamReader->m_bmpInfo.biHeight * 3;
    streamReader->m_RGB24Buffer = (BYTE*)malloc(streamReader->m_RGB24BufferSize);

    DownloadStream((LPCTSTR)streamReader->dvrIp,  
        streamReader->dvrPort , (LPCTSTR)streamReader->userName , 
        (LPCTSTR)streamReader->password , channelBits, channelBits,
        streamReader->startTime, streamReader->milliSecond, 
        streamReader->OnNewImage, (void*)streamReader);

    streamReader->startTime = -2; // End Of Stream

    return 0;
}


/*
 * ******************
 * DVRPin Class
 * ******************
 */

DVRPin::DVRPin(HRESULT *phr, DVRSourceFilter *pFilter)
: CSourceStream(NAME("DVR Source Bitmap"), phr, pFilter, L"Out")
{


    m_bmpInfo.biSize = sizeof(BITMAPINFOHEADER);
    m_bmpInfo.biCompression = BI_RGB;
    m_bmpInfo.biBitCount = 24;
    m_bmpInfo.biPlanes = 1;
    m_bmpInfo.biClrImportant = 0;
    m_bmpInfo.biClrUsed = 0;
    m_bmpInfo.biXPelsPerMeter = 0;
    m_bmpInfo.biYPelsPerMeter = 0;

    hDecHandle = 0;
    m_RGB24Buffer = NULL;
    m_RGB24BufferSize = 0;
    streamCompleted = false;
    startTime = -1; // Live Stream


    *phr = S_OK;
}


DVRPin::~DVRPin()
{   
}


int DVRPin::OnNewImage(void *pUser, BYTE *pData, int nLen, int nCh, int tMts, int nType, void *returnHandle)
{

    DVRPin* reader = (DVRPin*)pUser;

    if(reader->streamCompleted)
    {
        return false;
    }

    if(pData) 
    {

        std::vector<BYTE> vecFrame(pData, pData + nLen/sizeof(pData[0]));
        reader->IncomingFramesQueue.Enqueue(vecFrame);

    }


    return  !reader->streamCompleted;
}
HRESULT DVRPin::OnThreadCreate() 
{
    m_hDVRStreamThreadHandle = 
        (HANDLE)_beginthreadex(NULL, 0, &DVRStreamThread, (void*)this, 0, &m_dwThreadID);

    return S_OK;
}

HRESULT DVRPin::OnThreadDestroy() {


    streamCompleted = true;
    _endthreadex(0);

    CloseHandle(m_hDVRStreamThreadHandle);

    return S_OK;
}


HRESULT DVRPin::GetMediaType(CMediaType *pMediaType)
{

    CAutoLock cAutoLock(m_pFilter->pStateLock());

    CheckPointer(pMediaType, E_POINTER);

    VIDEOINFOHEADER* pvi = (VIDEOINFOHEADER*)pMediaType->AllocFormatBuffer(sizeof(VIDEOINFOHEADER));
    if (pvi == 0) 
        return(E_OUTOFMEMORY);

    ZeroMemory(pvi, pMediaType->cbFormat);   

    pvi->bmiHeader = m_bmpInfo;
    pvi->bmiHeader.biSizeImage = GetBitmapSize(&pvi->bmiHeader);


    SetRectEmpty(&(pvi->rcSource)); 
    SetRectEmpty(&(pvi->rcTarget)); 

    pMediaType->SetType(&MEDIATYPE_Video);
    pMediaType->SetFormatType(&FORMAT_VideoInfo);
    pMediaType->SetTemporalCompression(FALSE);

    // Work out the GUID for the subtype from the header info.
    const GUID SubTypeGUID = __uuidof(Subtype_H264);//GetBitmapSubtype(&pvi->bmiHeader);
    pMediaType->SetSubtype(&SubTypeGUID);
    pMediaType->SetSampleSize(pvi->bmiHeader.biSizeImage);

    return S_OK;
}

HRESULT DVRPin::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest)
{

    HRESULT hr;
    CAutoLock cAutoLock(m_pFilter->pStateLock());

    CheckPointer(pAlloc, E_POINTER);
    CheckPointer(pRequest, E_POINTER);

    VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER*) m_mt.Format();

    if (pRequest->cBuffers == 0)
    {
        pRequest->cBuffers = 2;
    }
    pRequest->cbBuffer = pvi->bmiHeader.biSizeImage;

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

    if (Actual.cbBuffer < pRequest->cbBuffer) 
    {
        return E_FAIL;
    }

    return S_OK;
}



HRESULT DVRPin::FillBuffer(IMediaSample *pSample)
{

    if(!streamCompleted) 
    {
        CAutoLock cAutoLock(m_pLock);
        HRESULT hr;

        BYTE* pData = NULL;


        hr = pSample->GetPointer(&pData);
        if(FAILED(hr))
        {
            pSample->Release();
            return hr;
        }

        if(IncomingFramesQueue.Size() <= 0) {
            return S_OK;
        }

        vector<BYTE> data = IncomingFramesQueue.Dequeue();
        int dataSize = (int)data.size();

        if(dataSize <= 0 || dataSize > 1000000) 
        {
            return S_OK;
        }

        memcpy(pData, &data[0], dataSize);

        hr = pSample->SetActualDataLength(dataSize);
        if(FAILED(hr))
        {
            pSample->Release();
            return hr;
        }



        hr = pSample->SetSyncPoint(TRUE);
        if(FAILED(hr))
        {
            pSample->Release();
            return hr;
        }

        pSample->Release();

    }
    return S_OK;
}

HRESULT DVRPin::DoBufferProcessingLoop() {


    Command com;
    REFERENCE_TIME rtNow = 0L;
    REFERENCE_TIME rtAdvise = 0L;

    OnThreadStartPlay();

    do {
        while (!streamCompleted && !CheckRequest(&com)) {
            IncomingFramesQueue.WaitUntilHaveElements();

            IMediaSample *pSample;

            HRESULT hr = GetDeliveryBuffer(&pSample,NULL,NULL,FALSE);
            if (FAILED(hr)) {
                continue;   // go round again. Perhaps the error will go away
                // or the allocator is decommited & we will be asked to
                // exit soon.
            }


            hr = FillBuffer(pSample);


            if (hr == S_OK) {
                Deliver(pSample);
            } else if (hr == S_FALSE) {
                pSample->Release();
                DeliverEndOfStream();
                return S_FALSE;
            } else {

                // Log Error
            }

            pSample->Release();
        }

        if (com == CMD_RUN || com == CMD_PAUSE)
            com = GetRequest(); // throw command away
        else if (com != CMD_STOP) 
        {

            // Log Error

        }
    } while (!streamCompleted && com != CMD_STOP);

    return S_OK;
}

void DVRPin::SetDvrIp( char* dvrIp )
{
    this->dvrIp = dvrIp;
}

void DVRPin::SetDvrPort( int dvrPort )
{
       this->dvrPort = dvrPort;
}

void DVRPin::SetDvrUserName( char * userName )
{
       this->userName = userName;
}

void DVRPin::SetDvrPassword( char* password )
{
        this->password = password;
}

void DVRPin::SetStartTime( int startTime )
{
    this->startTime = startTime;
}

void DVRPin::SetMilliSecond( int milliSecond )
{
    this->milliSecond = milliSecond;
}

void DVRPin::SetSize(int width, int height) {
    m_bmpInfo.biWidth = width;
    m_bmpInfo.biHeight = height;
    m_bmpInfo.biSizeImage = GetBitmapSize(&m_bmpInfo);
}


char* DVRPin::GetDvrIp()
{
    return dvrIp;
}

int DVRPin::GetDvrPort()
{
    return dvrPort;
}

char* DVRPin::GetDvrUserName()
{
    return userName;

}

char* DVRPin::GetDvrPassword()
{
    return password;
}

int DVRPin::GetStartTime()
{
    return startTime;
}

int DVRPin::GetMilliSecond()
{
    return milliSecond;
}

void DVRPin::SetChannelNumber( int channelNumber )
{
    this->channelNumber = channelNumber;
}

int DVRPin::GetChannelNumber()
{
    return channelNumber;
}



/*
 * ****************************
 * DVRSourceFilter Class
 * ***************************
 */


DVRSourceFilter::DVRSourceFilter(IUnknown *pUnk, HRESULT *phr)
: CSource(NAME("DVRSourceBitmap"), pUnk, CLSID_DVRSourceFilter)
{


    // The pin magically adds itself to our pin array.
    m_pPin = new DVRPin(phr, this);

    // Just for test at graph studio
    SetDVRLiveParameters("192.168.3.151", 7000, "admin", "000000", 3, 352, 288);

    if (phr)
    {
        if (m_pPin == NULL)
            *phr = E_OUTOFMEMORY;
        else
            *phr = S_OK;
    }
}

DVRSourceFilter::~DVRSourceFilter()
{
    delete m_pPin;
}

CUnknown * WINAPI DVRSourceFilter::CreateInstance(IUnknown *pUnk, HRESULT *phr)
{
    DVRSourceFilter *pNewFilter = new DVRSourceFilter(pUnk, phr);

    if (phr)
    {
        if (pNewFilter == NULL) 
            *phr = E_OUTOFMEMORY;
        else
            *phr = S_OK;
    }

    return pNewFilter;
}

STDMETHODIMP DVRSourceFilter::NonDelegatingQueryInterface( REFIID riid, void **ppv )
{
    return CSource::NonDelegatingQueryInterface(riid, ppv);
}

void DVRSourceFilter::SetDVRLiveParameters( char* dvrIP, int dvrPort, char* userName, char* password, int channelNumber, int width, int height )
{
    m_pPin->SetDvrIp(dvrIP);
    m_pPin->SetDvrPort(dvrPort);
    m_pPin->SetDvrUserName(userName);
    m_pPin->SetDvrPassword(password);
    m_pPin->SetChannelNumber(channelNumber);
    m_pPin->SetStartTime(-1);// Live Stream
    m_pPin->SetMilliSecond(0);
    m_pPin->SetSize(width, height); 
}

...

To make directshow Filter simple [ to understand memory leak source], just implement OnNewImage function and FillBufferFunction as "dummy", but still has memory leak:

int DVRPin::OnNewImage(void *pUser, BYTE *pData, int nLen, int nCh, int tMts, int nType, void *returnHandle)
{

    return 1; // for not to end call back
}


HRESULT DVRPin::FillBuffer(IMediaSample *pSample)
{
     pSample->Release();
     return S_OK;
}
Novalis
  • 2,265
  • 6
  • 39
  • 63
  • a bug in the third party API? – wimh May 18 '12 at 14:53
  • Well, Wimmel...It does not generate "memory"leak in a simple console application...or winform application...So I can not blame 3rd party API. – Novalis May 18 '12 at 15:08
  • Does not leak in this simple use case, and might be leaking when used otherwise. If something is leaking and you did not manage to get details to suspect specific components, then you need to investigate it deeper and isolate the leak. – Roman R. May 18 '12 at 16:42
  • Well, I isolate its usage. I have simple queue which i insert incoming frames. The same code used with main does not produce any leak. So i just wonder if directshow framework has such impact-side effect? – Novalis May 21 '12 at 06:46
  • If DirectShow has impact - which already sounds weird to me - then it is anyway a bug in the third party API. Provided that the DirectShow fitler, supposedly yours, is clean from memory leaks - they are doing something wrong. Anyway, among the three: DirectShow, the filter (in case of custom filter), and the third party API, among those DirectShow is the least likely cause of the problem. – Roman R. May 21 '12 at 06:52
  • Do you have documentation for `DownloadStream` and assumptionson the callback function? – Attila May 21 '12 at 15:03
  • Well, simple doc just tell about parameters and their types shortly...nothing special – Novalis May 22 '12 at 07:33
  • Can we see your filter? That's where I expect the problem to be. Unless the callback function varies its behavior in response to some external state (global variables, for example), it's hard to imagine it leaking when called from your filter, but not from your console app. – Stevens Miller May 24 '12 at 11:52
  • Steven, I put important parts from my Source Filter...Look at my update section of my questions. – Novalis May 25 '12 at 13:09

2 Answers2

2

In DVRStreamThread, you have:

streamReader->m_RGB24Buffer = (BYTE*)malloc(streamReader->m_RGB24BufferSize);

But I don't see a matching call to free() anywhere. When your DVRPin object is deleted, you will have to explicitly free the data pointed to by its m_RGB24Buffer member.

Stevens Miller
  • 1,387
  • 8
  • 25
  • Well,It is a one time "allocation".I am not worried about it. The problem is that "memory consumtion is "increasing" by time while filter is working which can not be by this line of code. – Novalis Jun 01 '12 at 12:45
  • One-time allocation won't grow over time, that's true. But, I just noticed in your short version of `FillBuffer` that you call `pSample->Release()`, which is also called in your long version. `FillBuffer` shouldn't release the `IMediaSample`. `DoBufferProcessingLoop` is going to need it after `FillBuffer` returns, to pass it to `Deliver`. Your `DoBufferProcessingLoop` correctly releases it after `Deliver` returns. So, you are releasing it twice. Maybe that is messing up `IMemAllocator`'s management of the buffer pool. Take the `pSample->Release()` out of `FillBuffer` and see if that helps. – Stevens Miller Jun 01 '12 at 13:20
  • I removed pSample->Release() just just return S_OK from FillBuffer; And memeory still increase. – Novalis Jun 01 '12 at 13:33
  • Man, you got a tough one. Okay, you've trimmed down your filter to almost nothing, with a stub for `FillBuffer` and for your `OnNewImage` callback. One thing I notice is that the call signature for `OnNewImage` is different in your console program from what it is in your filter code. Why is that? The only difference of any significance I can see in your code that still runs repeatedly is the call to `IncomingFramesQueue.WaitUntilHaveElements()` in your `DoBufferProcessingLoop`. I know you said you disabled the queue code, but can you take that call out entirely? – Stevens Miller Jun 01 '12 at 14:48
  • Well, My Console and Filter callback signatures are the same.But just to make it simple I do not show full signature at console application. – Novalis Jun 01 '12 at 14:56
  • To give more info callback function is probbably written in C++6 where I use Visual C++ 9.... – Novalis Jun 01 '12 at 14:59
  • Sorry for wrong name...Well, it is DVRStreamThread(LPVOID pvParam) function which you can see its implementaion above..when i comment it there is no memeory leak – Novalis Jun 02 '12 at 07:22
  • Then it is that malloc call. You only call it once in DVRStreamThread, but if you are creating that thread more than once you are calling malloc each time you do it. – Stevens Miller Jun 02 '12 at 11:40
  • Then I go back to recommending that you take out the queue call in `DoBufferProcessingLoop`. – Stevens Miller Jun 04 '12 at 16:16
  • Well, i did...Actually i removed all DoBufferProcessingLoop function...Just FillBuffer left...nothing is changed... – Novalis Jun 05 '12 at 10:27
  • Sorry, brother. I'm out of ideas. – Stevens Miller Jun 05 '12 at 12:08
1

First thing I see, is that your destructors are not virtual. This might be a cause of leaks when release is not taking place when inheritance is used. See related article about the necessity of virtual destructors.

Community
  • 1
  • 1
mox
  • 6,084
  • 2
  • 23
  • 35
  • Well, a good point for general C++.But not true for my case: when i comment third part API DownloadStream function, i get no memory problems.It is not related with virtual:I make destructors virtual but nothing changed. – Novalis May 25 '12 at 13:29
  • This is why I said that it "might be a cause..", and in C++ it is one of the major reason for objects not beeing released. – mox May 25 '12 at 13:39
  • Another reason might be, that the DirectX COM object is copied (thus AddRef()) and thus the RefCounting is increased. This might be the reason why your interfaces are not freed. You might try to invoke Release() on the interface in your Queue. – mox May 25 '12 at 13:56
  • Memory usage is strictly "increasing" although i do not insert anything to Queue.[ Queue is my code and i disable everthing related with Queue ]... – Novalis May 25 '12 at 14:07