Date

プルモデルのソースフィルタを作成する。SDKのサンプルに、Async というサンプルがあるが、もっと単純なソースフィルタを作成する。機能としては、あるファイルをディスクから読み取り、バイナリデータを下流へ渡すフィルタとする。

プッシュモデルとプルモデルの違い

ピン間のデータ受け渡しの方式に、プッシュモデル と プルモデル という 2 つの方式がある。 プッシュモデルは、上流フィルタ (アップストリームフィルタ) がデータを生成し、生成後、下流フィルタ (ダウンストリームフィルタ) へ向かってデータを転送する。 プルモデルというのはその反対で、下流フィルタが上流フィルタに対してデータの要求を行い、上流フィルタがデータ生成して渡すというものである。これを非同期的に実行する。 GraphEdit を開いてフィルタのリストをみると、File Source (Async) というフィルタがある。

これは、ディスクからファイルを読み取って、バイナリデータを下流へ渡すプルモデルのフィルタである。プルモデルなので下流フィルタの要求に応じてディスクから読み取ってデータを受け渡すようになっている。 AVI スプリッタフィルタや、MPEG-1 ストリーム スプリッタフィルタへ接続するフィルタを作りたい場合は、プルモデルをサポートするフィルタを作成する必要がある。この制約条件は、それらフィルタの仕様によるものである。

フィルタクラスを実装する

プロジェクト名は、MyAsyncSrcFilter とする。MyAsyncSrcFilter.h を新規で作成し、stdafx.h でインクルードする。 MyAsyncSrcFilter.h は次のようにする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#pragma once

#define TEMPLATE_NAME    (L"My AsyncSrc Filter")
#define FILTER_NAME        (TEMPLATE_NAME)
#define OUTPUT_PIN_NAME  (L"Output")
// {B32CC60D-D546-4a90-8354-31F1487EB684}
DEFINE_GUID(CLSID_MyAsyncSrc,
0xb32cc60d, 0xd546, 0x4a90, 0x83, 0x54, 0x31, 0xf1, 0x48, 0x7e, 0xb6, 0x84);
// ピンタイプの定義
const AMOVIESETUP_MEDIATYPE sudPinTypes={&MEDIATYPE_Stream, &MEDIASUBTYPE_NULL};
// 入力ピンの情報
const AMOVIESETUP_PIN sudPins =
{
    OUTPUT_PIN_NAME,// 廃止
    FALSE,        // Is it rendered
    TRUE,        // Is it an output
    FALSE,        // Allowed none
    FALSE,        // Allowed many
    0,// 廃止
    0,// 廃止
    1,            // Number of types
    &sudPinTypes      // Pin information
};
// フィルタ情報
const AMOVIESETUP_FILTER afFilterInfo= {
    &CLSID_MyAsyncSrc,      // フィルタのCLSID
    FILTER_NAME,            // フィルタ名
    MERIT_DO_NOT_USE,       // メリット値
    1,              // ピン数
    &sudPins        // ピン情報
};
// フィルタクラス
class CMyAsyncSrc : public CBaseFilter {
public:
    DECLARE_IUNKNOWN;
    CMyAsyncSrc (LPUNKNOWN pUnk, HRESULT *phr);
    ~CMyAsyncSrc ();
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN, HRESULT *);
    int GetPinCount();
    CBasePin *GetPin(int n);
    HRESULT Connect(IPin * pReceivePin,const AM_MEDIA_TYPE *pmt);
private:
    CAsyncOutPin *  m_pOutPin; // <!> 出力ピンクラス。とりあえず後回し。
    CCritSec        m_lock;
};

ここで注目して頂きたいのは、フィルタクラスCMyAsyncSrcの定義である。 継承しているクラスがCBaseFilterとなっている。 プッシュモデルではCSourceですが、プルモデルではCBaseFilterとなる。CBaseFilterはフィルタの基本的な機能を提供する基底クラスである。 CSourceと比較して、少し実装量が増える。 次にCMyAsyncSrcクラスを実装する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "stdafx.h"
#include <exception>
CUnknown * WINAPI CMyAsyncSrc::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr) {
    CMyAsyncSrc *pNewFilter = NULL;
    try {
        pNewFilter = new CMyAsyncSrc(pUnk, phr );
    } catch (std::bad_alloc) {
        *phr = E_OUTOFMEMORY;
    }
    return dynamic_cast<CUnknown *>(pNewFilter);
}

CMyAsyncSrc::CMyAsyncSrc(IUnknown *pUnk, HRESULT *phr) :
    CBaseFilter(FILTER_NAME,pUnk,&m_lock,CLSID_MyAsyncSrc,NULL)
{
    m_pOutPin = new CAsyncOutPin(phr,this,&m_lock);
}
CMyAsyncSrc::~CMyAsyncSrc() {
    if(m_pOutPin){
        delete m_pOutPin;
        m_pOutPin=NULL;
    }
}
int CMyAsyncSrc::GetPinCount() {
    return 1;
}
CBasePin *CMyAsyncSrc::GetPin(int n) {
    if(GetPinCount()>0 && n==0) {
        return m_pOutPin;
    }
    return NULL;
}
HRESULT CMyAsyncSrc::Connect(IPin * pReceivePin,const AM_MEDIA_TYPE *pmt) {
    return m_pOutPin->CBasePin::Connect(pReceivePin, pmt);
}

CBaseFilterの純粋仮想メソッドを実装する。CBaseFilter::GetPinCountCBaseFilter::GetPinの2つである。 GetPinCountは、出力ピンの数を返する。ここでは 1 固定である。 GetPinは、引数 n 番目のピンをCBasePinへのポインタとして返している。ここでは、出力ピンクラスCAsyncOutPinのポインタm_pOutPinを返している。(実装は後で) フィルタクラスの実装としては、これで全てである。ファイルに読み取ったり、下流フィルタの要求に応える部分はCAsyncOutPinで行うこととする。 出力ピンクラスCAsyncOutPinのインスタンスは、コンストラクタでnewしている。

IAsyncReader をサポートする出力ピンクラスを実装する

MyAsyncSrcFilter.hにCAsyncOutPinクラスの定義を追加する。IAsyncReader, CBasePinを継承する。 また構造体Samplesを定義する。プルモデルの場合、データ要求と受け渡しが非同期的に行われるためキュー構造を定義しておくと便利である。キューは STL のstd::queueを使って実現する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#define ALLOCATOR_NAME  (L"My Allocator")
class CMyAsyncSrc;
// 読み取った 1 つのサンプル
struct Samples{
    IMediaSample*    pMs;
    DWORD            user;
};
// 非同期出力ピン
class CAsyncOutPin : public IAsyncReader, public CBasePin {
public:
    DECLARE_IUNKNOWN;
    CAsyncOutPin(HRESULT * phr,CBaseFilter *pFilter,CCritSec * pLock);
    ~CAsyncOutPin();
    STDMETHODIMP NonDelegatingQueryInterface(REFIID, void**);
    // CBasePin
    STDMETHODIMP Connect(IPin * pReceivePin,const AM_MEDIA_TYPE *pmt);
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
    HRESULT CheckMediaType(const CMediaType* pType);
    HRESULT CheckConnect(IPin *pPin);
    HRESULT CompleteConnect(IPin *pReceivePin);
    HRESULT BreakConnect();
    // IAsyncReader
    STDMETHODIMP RequestAllocator(IMemAllocator* pPreferred,ALLOCATOR_PROPERTIES* pProps,IMemAllocator ** ppActual);
    STDMETHODIMP Request(IMediaSample* pSample,DWORD_PTR dwUser);
    STDMETHODIMP WaitForNext(DWORD dwTimeout,IMediaSample** ppSample,DWORD_PTR * pdwUser);
    STDMETHODIMP SyncReadAligned(IMediaSample* pSample);
    STDMETHODIMP SyncRead(LONGLONG llPosition,LONG lLength,BYTE* pBuffer);
    STDMETHODIMP Length(LONGLONG* pTotal,LONGLONG* pAvailable);
    STDMETHODIMP BeginFlush(void);
    STDMETHODIMP EndFlush(void);
private:
    HRESULT InitAllocator(IMemAllocator **ppAlloc);
    HRESULT ReadData(LONGLONG pos, LONG length, BYTE *pBuffer, DWORD *pReadSize=NULL);
    bool            m_bQueriedForAsyncReader;
    CMyAsyncSrc *    m_pFilter;
    CMediaType        m_mt;
    HANDLE            m_hFile;
    HANDLE            m_hWait;
    std::queue<Samples> m_Data;
    bool            m_Flush;
    CCritSec        m_DataLock;
    CCritSec        m_ReadLock;
};

御覧のとおり、実装するメソッドが多いが粛々と進める。 AsyncOutPin.cpp を新規作成し、ここに実装していく。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include "stdafx.h"

#define FILENAME TEXT("c:\\windows\\clock.avi")
CAsyncOutPin::CAsyncOutPin(HRESULT * phr,CBaseFilter *pFilter,CCritSec * pLock)
  : CBasePin(OUTPUT_PIN_NAME,pFilter,pLock,phr,OUTPUT_PIN_NAME,PINDIR_OUTPUT)
  , m_bQueriedForAsyncReader(false) , m_hFile(INVALID_HANDLE_VALUE)
{
    m_pFilter = dynamic_cast<CMyAsyncSrc*>(pFilter);
    m_mt.majortype = MEDIATYPE_Stream;
    m_mt.subtype   = MEDIASUBTYPE_Avi;
    m_hFile=CreateFile(FILENAME, GENERIC_READ
        ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    m_hWait=CreateEvent(NULL,TRUE,FALSE,NULL);
    m_Flush=false;
}
CAsyncOutPin::~CAsyncOutPin() {
    CloseHandle(m_hFile);
    CloseHandle(m_hWait);
}

まずコンストラクタだがCBasePinを継承しているので、CBasePin のコンストラクタに引数を渡している。 m_bQueriedForAsyncReaderを false として初期化している。m_bQueriedForAsyncReaderは、下流フィルタの入力ピンがIAsyncReaderインターフェイスを問い合わせたときに true となるフラグである。今回はプッシュモデルを期待しているフィルタには接続できないこととする。 m_mtCMediaTypeである。メディアタイプは、メジャー Stream, サブタイプ Avi として固定している。MPEG など他の形式の場合は変更すること。今回はバイナリデータを下流フィルタへ流すので、メジャータイプは Stream 固定となる。 m_hFileはファイルハンドルである。ここでは c:\windows\clock.avi を開く処理をしている。 m_hWaitはイベントのハンドルである。データの要求と読み取りが非同期的に実行されるため、それらの制御を行うために作成している。下流フィルタからデータの要求があり、ファイル読み取りが完了した時点でシグナルが ON になる。 m_Flushはフラッシュ状態のフラグである。フラッシュはシーク実行前や、(ユーザ操作による)フィルタグラフ停止時など読み取ったサンプルを破棄する処理である。フラッシュ状態になったときにm_Flush=trueとなるようにする。

1
2
3
4
5
6
7
8
9
STDMETHODIMP CAsyncOutPin::NonDelegatingQueryInterface(REFIID riid, void** ppv) {
    CheckPointer(ppv,E_POINTER);
    if(riid == IID_IAsyncReader) {
        DbgLog((LOG_TRACE,5,TEXT("QI , riid=IID_IAsyncReader") ));
        m_bQueriedForAsyncReader=true;
        return GetInterface(dynamic_cast<IAsyncReader*>(this), ppv);
    }
    return __super::NonDelegatingQueryInterface(riid, ppv);
}

NonDelegatingQueryInterfaceではriidで指定されたインターフェイスを返すメソッドである。ここではIID_IAsyncReaderを指定されたときに当該インターフェイスを返している。またm_bQueriedForAsyncReader=trueとしている。

1
2
3
4
5
STDMETHODIMP CAsyncOutPin::Connect(IPin * pReceivePin,const AM_MEDIA_TYPE *pmt) {
    CheckPointer(m_pFilter,E_UNEXPECTED);
    DbgLog((LOG_TRACE,5,TEXT("Connect") ));
    return m_pFilter->Connect(pReceivePin, pmt);
}

Connectメソッドはピンが他のピンに接続されたときの処理である。CAsyncSrcFilterを追いかけると分かるがCBasePin::Connectを呼んでるだけである。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
HRESULT CAsyncOutPin::GetMediaType(int iPosition, CMediaType *pMediaType) {
    if(iPosition < 0)
    return E_INVALIDARG;
    if(iPosition > 0)
    return VFW_S_NO_MORE_ITEMS;
    CheckPointer(pMediaType,E_POINTER);
    CheckPointer(m_pFilter,E_UNEXPECTED);
    DbgLog((LOG_TRACE,5,TEXT("GetMediaType") ));
    *pMediaType = m_mt;
    return S_OK;
}
HRESULT CAsyncOutPin::CheckMediaType(const CMediaType* pType) {
    CAutoLock lck(m_pLock); // STATE LOCK
    if(m_mt.majortype == pType->majortype && m_mt.subtype == pType->subtype){
        DbgLog((LOG_TRACE,5,TEXT("CheckMediaType S_OK") ));
        return S_OK;
    }
    DbgLog((LOG_TRACE,5,TEXT("CheckMediaType S_FALSE") ));
    return S_FALSE;
}

GetMediaTypeCheckMediaType を実装する。 GetMediaTypeではiPosition == 0以外受け付けないようにしている。0 のときはm_mtを返す。(コンストラクタで設定済) CheckMediaTypeでは、引数pTypeで渡されたメディアタイプを接続していいか判定している。m_mtで定義してあるメディアタイプと一致するのが条件としている。CAutoLockでクリティカルセクションを張っているがm_mtは固定されてるし、それとの比較だけなので不要な気がする…が、サンプルコードのCheckMediaTypeではそうなっていたので残している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
HRESULT CAsyncOutPin::CheckConnect(IPin *pPin) {
    DbgLog((LOG_TRACE,5,TEXT("CheckConnect") ));
    m_bQueriedForAsyncReader = false;
    return CBasePin::CheckConnect(pPin);
}
HRESULT CAsyncOutPin::CompleteConnect(IPin *pReceivePin) {
    if(m_bQueriedForAsyncReader){
        DbgLog((LOG_TRACE,5,TEXT("CompleteConnect") ));
        return CBasePin::CompleteConnect(pReceivePin);
    }
    DbgLog((LOG_TRACE,5,TEXT("CompleteConnect VFW_E_NO_TRANSPORT") ));
    return VFW_E_NO_TRANSPORT;
}
HRESULT CAsyncOutPin::BreakConnect() {
    DbgLog((LOG_TRACE,5,TEXT("BreakConnect") ));
    m_bQueriedForAsyncReader = false;
    return CBasePin::BreakConnect();
}

接続時に呼び出されるメソッドである。 CompleteConnectm_bQueriedForAsyncReaderを確認し、true でなければ接続できないようにしている。 CheckConnectBreakConnectm_bQueriedForAsyncReader=false に戻している。 CheckConnectfalse にし、NonDelegatedQueryInterface が呼ばれると true になる。そのまま CompleteConnect まで処理が進めば、すなわち接続処理が完了すれば接続する。そうでない場合は VFW_E_NO_TRANSPORT を返して接続を止める。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
STDMETHODIMP CAsyncOutPin::RequestAllocator(IMemAllocator* pPreferred
                                            ,ALLOCATOR_PROPERTIES* pProps,IMemAllocator ** ppActual){
    HRESULT hr = NOERROR;
    CheckPointer(pPreferred,E_POINTER);
    CheckPointer(pProps,E_POINTER);
    CheckPointer(ppActual,E_POINTER);
    DbgLog((LOG_TRACE,5,TEXT("RequestAllocator") ));
    ALLOCATOR_PROPERTIES Actual;
    IMemAllocator* pAlloc=NULL;
    if(pPreferred) {
    hr = pPreferred->SetProperties(pProps, &Actual);
    pPreferred->AddRef();
    *ppActual = pPreferred;
        return hr;
    }
    hr = InitAllocator(&pAlloc);
    if(FAILED(hr)) {
        return hr;
    }
    hr = pAlloc->SetProperties(pProps, &Actual);
    if(SUCCEEDED(hr)) {
        *ppActual = pAlloc;
        return hr; // S__OK
    }
    pAlloc->Release();
    if(SUCCEEDED(hr)) {
        hr = VFW_E_BADALIGN;
    }
    return hr;
}

いよいよIAsyncReaderの実装に入っていく。 まずRequestAllocatorである。これはピンの接続中にダウンストリームの入力ピンから呼び出される。 アロケータは読み取ったサンプルを記憶する領域を確保するためのインターフェイスとして使われる。引数 pPreffered に入力ピンの優先アロケータが代入されてる場合は、それを使う。 そうでなければ、自前で用意 (InitAllocator) している。 SetPropertiesで、メモリサイズや alignment といったアロケータプロパティを設定している。基本的に引数pPropsの内容をそのまま渡ずたけで 良い。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
HRESULT CAsyncOutPin::InitAllocator(IMemAllocator **ppAlloc) {
    CheckPointer(ppAlloc,E_POINTER);
    DbgLog((LOG_TRACE,5,TEXT("InitAllocator") ));
    HRESULT hr = NOERROR;
    CMemAllocator *pMemObject = NULL;
    *ppAlloc = NULL;
    pMemObject = new CMemAllocator(ALLOCATOR_NAME, NULL, &hr);
    if(pMemObject == NULL) {
        return E_OUTOFMEMORY;
    }
    if(FAILED(hr)) {
        delete pMemObject;
        return hr;
    }
    hr = pMemObject->QueryInterface(IID_IMemAllocator,(void **)ppAlloc);
    if(FAILED(hr)) {
        delete pMemObject;
        return E_NOINTERFACE;
    }
    ASSERT(*ppAlloc != NULL);
    return S_OK;
}

InitAllocator メソッドである。入力ピンから優先アロケータを渡されなかった場合は、出力ピン、つまりこちら側が用意してあげる必要がある。CMemAllocator が用意されているので、それを使う。これを使うとメモリ上にバッファを確保してくれる。 ここから、データの要求や受け渡しを行うメソッドを実装するのだが、その前にファイルを読み込むメソッドを実装してしまおう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
HRESULT CAsyncOutPin::ReadData(LONGLONG pos, LONG length, BYTE *pBuffer, DWORD *pReadSize) {
    CAutoLock mylock(&m_ReadLock);
    DWORD readsize=0;
    BOOL result=FALSE;
    LARGE_INTEGER seekto;
    LARGE_INTEGER seekedpos;
    seekto.QuadPart =pos;
    result=SetFilePointerEx(m_hFile, seekto, &seekedpos, FILE_BEGIN);
    if(result==0){
        return E_FAIL;
    }
    result=ReadFile(m_hFile, pBuffer, length, &readsize, NULL);
    if(pReadSize!=NULL)
        *pReadSize=readsize;
    if(result==0) {
        return E_FAIL;
    }
    return S_OK;
}

バイト単位で任意の位置 pos から、指定された長さ length 分だけを pBufferへ格納する。呼び出し元が非同期的に処理されるので、スレッドセーフになるようにCAutoLockを行っている。実際に読み取ったサイズは pReadSize に代入している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
STDMETHODIMP CAsyncOutPin::Request(IMediaSample* pSample,DWORD_PTR dwUser){
    CheckPointer(pSample,E_POINTER);
    DbgLog((LOG_TRACE,5,TEXT("Request pSample=%p dwUser=%lu"),pSample, dwUser ));
    REFERENCE_TIME tStart, tStop;
    if(m_Flush){
        // フラッシュ状態のときは、これ以上要求を受け付けない.
        SetEvent(m_hWait);
        return VFW_E_WRONG_STATE;
    }
    HRESULT hr = pSample->GetTime(&tStart, &tStop);
    if(FAILED(hr)) {
        SetEvent(m_hWait);
        return hr;
    }
    LONGLONG llPos  = tStart / UNITS;
    LONG     lLength= (LONG) ((tStop - tStart) / UNITS);
    LONGLONG llTotal=0;
    LONGLONG llAvailable=0;
    BYTE* pBuffer = NULL;
    hr = pSample->GetPointer(&pBuffer);
    if(FAILED(hr)) {
        SetEvent(m_hWait);
    return hr;
    }
    DWORD readsize;
    hr=ReadData(llPos,lLength,pBuffer,&readsize);
    if(FAILED(hr)){
        SetEvent(m_hWait);
        return E_FAIL;
    }
    pSample->SetActualDataLength(readsize);
    Samples s={pSample,dwUser};
    {
        CAutoLock mylock(&m_DataLock);
        m_Data.push(s);
        hr=S_OK;
        SetEvent(m_hWait);
        DbgLog((LOG_TRACE,5,TEXT("Wrote %lu"), dwUser));
    }
    return hr;
}
STDMETHODIMP CAsyncOutPin::WaitForNext(DWORD dwTimeout
                                       , IMediaSample** ppSample, DWORD_PTR * pdwUser) {
    HRESULT hr=S_OK;
    CheckPointer(ppSample,E_POINTER);
    bool e=false;
    *ppSample=NULL;
    *pdwUser=0;
    DbgLog((LOG_TRACE,5,TEXT("WaitForNext timeout=%d ppSample=%p pdwUser=%p dwUser=%lu flush=%d")
        ,dwTimeout, ppSample, pdwUser, *pdwUser, m_Flush));
    if(WaitForSingleObject(m_hWait,dwTimeout)==WAIT_TIMEOUT){
        DbgLog((LOG_TRACE,5,TEXT("WAIT_TIMEOUT")));
        return VFW_E_TIMEOUT;
    }
    {
        CAutoLock mylock(&m_DataLock);
        DbgLog((LOG_TRACE,5,TEXT("locked m_Flush=%d"), m_Flush));
        if(!m_Data.empty()){
            Samples s = m_Data.front();
            *ppSample = s.pMs;
            *pdwUser = s.user;
            DbgLog((LOG_TRACE,5,TEXT("s.user = %x"),s.user));
            m_Data.pop();
            if(m_Data.empty()){
                ResetEvent(m_hWait);
                DbgLog((LOG_TRACE,5,TEXT("empty")));
            }
            hr=S_OK;
        }else{
            if(m_Flush){
                hr=VFW_E_WRONG_STATE;
            }else{
                // キューに何もない && フラッシュ状態でない は, ありえないはず...
                DbgLog((LOG_TRACE,5,TEXT("E_FAIL")));
                hr=E_FAIL;
            }
        }
        DbgLog((LOG_TRACE,5,TEXT("unlocked HRESULT=%X"), hr));
    }
    return hr;
}

やや長くなったが、データの要求と受け渡しについて実装している。 下流フィルタの入力ピンからWaitForNextRequestが呼ばれる。これは異なるスレッドから呼ばれることがある。 Requestメソッドでは、渡されたIMediaSample *pSampleに対して、ファイルから読み込んだデータを代入している。このとき、ファイルのどの位置のデータを必要としているかはGetTimeメソッドを使う。 データを読み込んだら、キューm_DataSamplesのインスタンスを 1 つ push している。このときpSampleだけでなく引数dwUserで渡された値も push しておく。(そのためにSamples構造体を定義した。) 一方WaitForNextでは、キューm_Dataから 1 つ取り出してppSampleに代入している。 また、引数pdwUserに push したときのdwUserを代入している。 このようにdwUserの値をRequestWaitForNextで対応付けすることにより、どの要求したデータが受け取れたか、というのが入力ピン側で判定できるようになっている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
STDMETHODIMP CAsyncOutPin::SyncReadAligned(IMediaSample* pSample){
    DbgLog((LOG_TRACE,5,TEXT("SyncReadAligned pSample=%p"),pSample));
    CheckPointer(pSample,E_POINTER);
    REFERENCE_TIME tStart, tStop;
    HRESULT hr = pSample->GetTime(&tStart, &tStop);
    LONGLONG llPos  = tStart / UNITS;
    LONG     lLength= (LONG) ((tStop - tStart) / UNITS);
    LONGLONG llTotal=0;
    LONGLONG llAvailable=0;
    BYTE* pBuffer = NULL;
    hr=pSample->GetPointer(&pBuffer);
    if(FAILED(hr)){
        return hr;
    }
    hr=ReadData(llPos,lLength,pBuffer);
    return hr;
}
STDMETHODIMP CAsyncOutPin::SyncRead(LONGLONG llPosition,LONG lLength,BYTE* pBuffer){
    DbgLog((LOG_TRACE,5,TEXT("SyncRead pos=%I64d len=%ld buf=%p"),llPosition, lLength, pBuffer ));
    HRESULT hr=ReadData(llPosition, lLength, pBuffer);
    return hr;
}
STDMETHODIMP CAsyncOutPin::Length(LONGLONG* pTotal,LONGLONG* pAvailable){
    DbgLog((LOG_TRACE,5,TEXT("Length") ));
    LARGE_INTEGER x;
    GetFileSizeEx(m_hFile,&x);
    *pTotal    =x.QuadPart;
    *pAvailable=x.QuadPart;
    return S_OK;
}
// フラッシュ開始
// 未処理の読み取り要求をすべて中断する.
// 以後、EndFlushが呼ばれるまで、
// Request, WaitForNext メソッドは VFW_E_WRONG_STATE を戻り値として失敗する。
STDMETHODIMP CAsyncOutPin::BeginFlush(void){
    DbgLog((LOG_TRACE,5,TEXT("BeginFlush")));
    CAutoLock mylock(&m_DataLock);
    m_Flush=true;
    SetEvent(m_hWait); // イベントシグナル ON にして WaitForNext の シグナル待ちを解放
    DbgLog((LOG_TRACE,5,TEXT("BeginFlush Done")));
    return S_OK;
}
// フラッシュ終了
// 以後、Request メソッドが使用可能になること.
STDMETHODIMP CAsyncOutPin::EndFlush(void){
    DbgLog((LOG_TRACE,5,TEXT("EndFlush")));
    {
        CAutoLock mylock(&m_DataLock);
        m_Flush=false;
        m_Data = std::queue<Samples>();
        ResetEvent(m_hWait);
    }
    return S_OK;
}

ソースコード(GitHub)

https://github.com/mahorigahama/MyAsyncSrcFilter_src

注意

  • 任意のファイル位置から読みとれるフィルタである。従ってalignment を考慮していない。場合によっては、8バイト単位でしか読み取れない…なんていう条件が付くかもしれない。そのときは alignment を考慮したコーディングが必要になる。SDK サンプルの async が参考になる。
  • Requestメソッドは、ここでは同期的にデータの読み取りを行っている。しかし本来ならデータの要求を受け付けたら、すぐに処理を戻すべきである。たとえば TCP/IP ネットワーク上から読み取るのであれば、その処理は別スレッドで行うべきだろう。
  • できるだけ単純なサンプルコードになるように、エラー処理を省いている。実際にフィルタを作るときには、HRESULT コードをきちんと返すようにすると良いだろう。

実行結果

GraphEdit でフィルタグラフを確認する。


Comments

comments powered by Disqus