SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Advanced/W-ZERO3 [es]で作ろう

Advanced/W-ZERO3 [es]で簡易音声レコーダを作ろう Part 2

Windows MobileにおけるDirectShowフィルタの実装例

ダウンロード サンプルソース (36.6 KB)

WAV出力DirectShowフィルタの作成 2

フィルタクラスの作成

 DirectShowフィルタはIBaseFilterインターフェースを継承したクラスとして実装します。しかし、これを直接継承して実装した場合、コード量が多くなるので、SDKが提供しているクラスライブラリを使います。

 クラスライブラリにはフィルタの種類ごとに基底クラスが用意されていて、それらを派生させることにより独自のフィルタを容易に実装できます。今回作成するフィルタはオーディオのレンダラフィルタなので、CBaseRendererを派生させてCWavWriterクラスを作成します。クラス図を図1に示します。

図1.CWavWriterクラス図
図1.CWavWriterクラス図

 まずはメンバ変数を定義します。必要なメンバ変数としては、WAVファイルのフォーマット情報、ファイルハンドル、クリティカルセクションのオブジェクトです。

 WAVファイルのフォーマット情報はWAVEFORMATEX構造体へのポインタとします。これは、フォーマットによって構造体のサイズが一定ではないため動的に確保する必要があるためです。

 クリティカルセクションのオブジェクトはファイル出力処理をマルチスレッドで実行したときに不整合を起こさないために使います。ここではクラスライブラリで提供されているCCritSecクラスを使います。このクラスはCAutoLockクラスと組み合わせると、スコープを抜けた時点で自動的にクリティカルセクションを抜けるため便利です。

フィルタクラスのメソッドは複数のスレッドから呼ばれる
 フィルタグラフを実行したとき、2つ以上のスレッドが存在することになります。1つは開始や停止など、状態を変更するアプリケーションスレッド。もう1つはサンプルの処理をするストリーミングスレッドで、これは複数存在することもあります。
 フィルタクラスのメソッドは、アプリケーションスレッド、ストリーミングスレッド、または、その両方から呼ばれるものがあります。
 ファイルを出力するフィルタの場合、ストリーミングスレッド側でファイルを出力中に、アプリケーションスレッド側でフィルタグラフを停止した場合、出力完了までファイルを閉じることのないように排他制御しなければなりません。
 
WavWriter.h
class CWavWriter : public CBaseRenderer {
    ULONG m_ulSizeOfWfx; // WAVEFORMATEX 構造体のサイズ
    DWORD m_dwWavSize; // 波形データのサイズ
    WAVEFORMATEX *m_pWfx; // WAVEFORMATEX 構造体へのポインタ
    HANDLE m_hFile; // ファイルハンドル
    CCritSec m_CritSec; // ストリーミングスレッドと
                        // アプリケーションスレッドの排他制御用

 次にインスタンスを作成する静的なメソッドCreateInstance、コンストラクタ、デストラクタを定義します。

 CreateInstanceは、CoCreateInstanceでフィルタのインスタンスが作成される際に呼び出されます。ここでは、new演算子を使ってフィルタクラスのインスタンスを作成し返しています。図1のクラス図のように、CWavWriterクラスは戻り値の型であるCUnknownを継承しているので、そのまま返せます。

 コンストラクタではスーパークラスであるCBaseRendererのコンストラクタへ引数を渡して初期化します。デストラクタではWAVEFORMATEX構造体へのポインタが使われていた場合、deleteしています。

WavWriter.h
public:
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, 
                                            HRESULT *phr) {
        CWavWriter *pNewFilter = new CWavWriter(pUnk, phr);
        if (pNewFilter == NULL) {
            *phr = E_OUTOFMEMORY;
        }
        return pNewFilter;
    }
    CWavWriter(LPUNKNOWN pUnk,HRESULT *phr) :
        CBaseRenderer(CLSID_WavWriter,FILTER_NAME,pUnk,phr)
        , m_pWfx(NULL), m_ulSizeOfWfx(0), m_dwWavSize(0), 
          m_hFile(INVALID_HANDLE_VALUE) 
    {
    }
    virtual ~CWavWriter() {
        if(m_pWfx)
            delete[] m_pWfx;
    }

 デバッグビルド時のみNonDelegatingReleaseが必要となるため、これを定義します。これはINonDelegatingUnknownインターフェースで定義されているメソッドです。

WavWriter.h
#ifdef DEBUG
    STDMETHODIMP_(ULONG) NonDelegatingRelease() {
        return CUnknown::NonDelegatingRelease();
    }
#endif

 いよいよフィルタのコアとなる部分を作りこんでいきます。フィルタとして動作するためのほとんどのメソッドがCBaseRendererで実装済みですが、CheckMediaTypeDoRenderSampleは純粋仮想関数になっているため実装が必要です。ここでは、これら2つに加えOnStartStreamingOnStopStreamingも実装します。

 CheckMediaTypeでは、上流フィルタの出力ピンから接続されようとするときに呼び出されます。ここで行うべき処理は、メソッド名のとおりメディアタイプをチェックします。CMediaTypeとして渡されるメディアタイプをチェックし、受け入れ可能であればS_OK、そうでなければS_FALSEを返します。

 もし受け入れ可能である場合、フォーマット情報をWAVEFORMATEX構造体として取得します。

WavWriter.h
HRESULT CheckMediaType(const CMediaType *pMediaType) {
    CAutoLock mylock(&m_CritSec);
    if(!IsEqualGUID(pMediaType->majortype, MEDIATYPE_Audio) ||
        !IsEqualGUID(pMediaType->subtype, MEDIASUBTYPE_PCM)) {
        return S_FALSE;
    }
    // メディアのフォーマットの型が WAVEFORMATEX 以外は S_FALSE
    if(!IsEqualGUID(*pMediaType->FormatType()
        , FORMAT_WaveFormatEx)) {
        return S_FALSE;
    }
    WAVEFORMATEX *pWfx=(WAVEFORMATEX *)pMediaType->Format();
    m_ulSizeOfWfx=pMediaType->FormatLength();
    if(m_pWfx)
        delete[] m_pWfx;
    m_pWfx=(WAVEFORMATEX *)new BYTE[m_ulSizeOfWfx];
    CopyMemory(m_pWfx,pWfx,m_ulSizeOfWfx);
    return S_OK;
}

 DoRenderSampleでは、サンプル(音声データ)が届けられたときに呼び出されます。サンプルはIMediaSampleインターフェースで渡されます。GetPointerを使うと実際の音声データへのポインタを取得できます。データサイズはGetActualDataLengthでバイト単位で取得します。ここでは渡された音声データとそのサイズを取得し、WriteFileを使って音声データをファイルへ出力します。

WavWriter.h
HRESULT DoRenderSample(IMediaSample *pMediaSample) {
    CAutoLock mylock(&m_CritSec);
    if(m_hFile == INVALID_HANDLE_VALUE) {
        return S_OK;
    }
    LPBYTE pData;
    DWORD dwSize,dwWritten;
    pMediaSample->GetPointer(&pData);
    dwSize=pMediaSample->GetActualDataLength();
    WriteFile(m_hFile, pData, dwSize, &dwWritten, NULL);
    m_dwWavSize += dwSize;
    return S_OK;
}

 OnStartStreamingメソッドは、フィルタの状態が開始に遷移したときに呼び出されます。ここではCreateFileでファイルを新規作成し、WAVファイルのヘッダを出力します。ただしデータサイズは開始時点で決まっていないのでダミーで埋めます。

WavWriter.h
HRESULT OnStartStreaming(void) {
    CAutoLock mylock(&m_CritSec);
    m_hFile = CreateFile(TEXT("\\my documents\\test_rec.wav")
        , GENERIC_WRITE, 0, NULL 
        , CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(m_hFile == INVALID_HANDLE_VALUE) {
        return E_FAIL;
    }
    DWORD dwZero = 0; // ダミーです
    DWORD dwWritten;
    WriteFile(m_hFile, "RIFF", 4, &dwWritten, NULL);
    WriteFile(m_hFile, &dwZero, sizeof(DWORD), &dwWritten, NULL);
    WriteFile(m_hFile, "WAVE", 4, &dwWritten, NULL);
    WriteFile(m_hFile, "fmt ", 4, &dwWritten, NULL);
    WriteFile(m_hFile, &m_ulSizeOfWfx, sizeof(DWORD), 
                                       &dwWritten, NULL);
    WriteFile(m_hFile, m_pWfx, m_ulSizeOfWfx, &dwWritten, NULL);
    WriteFile(m_hFile, "data", 4, &dwWritten, NULL);
    WriteFile(m_hFile, &dwZero, sizeof(DWORD), &dwWritten, NULL);
    return S_OK;
}

 OnStopStreamingメソッドは、フィルタの状態が停止に遷移したときに呼び出されます。ここではヘッダにデータサイズなどを書き込んで、ファイルを閉じます。

WavWriter.h
HRESULT OnStopStreaming(void) {
    CAutoLock mylock(&m_CritSec);
    if(m_hFile == INVALID_HANDLE_VALUE) {
        return S_OK;
    }
    // ファイルを閉じる前にデータサイズをヘッダに書く
    // after "RIFF"
    SetFilePointer(m_hFile, 4,NULL,FILE_BEGIN);
    DWORD dwSize,dwWritten;
    dwSize=4+4+4+m_ulSizeOfWfx+4+4+m_dwWavSize;
    WriteFile(m_hFile, &dwSize, sizeof(DWORD), &dwWritten, NULL);
    // after "data"
    SetFilePointer(m_hFile, 20+m_ulSizeOfWfx+4,NULL,FILE_BEGIN);
    dwSize=m_dwWavSize;
    WriteFile(m_hFile, &dwSize, sizeof(DWORD), &dwWritten, NULL);
    CloseHandle(m_hFile);
    m_hFile=INVALID_HANDLE_VALUE;
    return S_OK;
}

レジストリに登録できるようにする

 フィルタクラスを実装したら、フィルタをレジストリへ登録できるようにします。前回の記事で示したように、フィルタのクラスIDを指定してCoCreateInstanceを呼び出すことによりフィルタのインスタンスを作成できます。このとき、クラスIDからDLLを特定するためにレジストリが参照されます。

 実装手順を以下に示します。

  1. CFactoryTemplateクラスのグローバル変数をg_Templatesという名前で宣言しフィルタ情報で初期化する(表4)
  2. int g_cTemplatesを宣言し、フィルタ情報の数で初期化する。
  3. レジストリへ登録するDllRegisterServer関数と、解除するDllUnregisterServer関数を定義する。
  4. DllMain関数を定義し、DllEntryPoint関数を呼び出す。
表4. CFactoryTemplateの内容
m_Name フィルタ名
m_ClsID クラスIDへのポインタ
m_lpfnNew インスタンスを作成する関数へのポインタ
m_lpfnInit DLLエントリポイントから呼び出される関数へのポインタ(今回は未使用)
m_pAMovieSetup_Filter AMOVIESETUP_FILTER構造体へのポインタ

 レジストリへ登録および解除はAMovieDllRegisterServer2を使うと簡単にできます。

WavWriter.cpp
#include "stdafx.h"
#include "WavWriter.h" // フィルタ情報およびクラスが定義されている

extern "C" {
    BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
};

CFactoryTemplate g_Templates [] = {
    { FILTER_NAME , &CLSID_WavWriter,
    CWavWriter::CreateInstance, NULL, &afFilterInfo }
};

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

STDAPI DllRegisterServer() {
    return AMovieDllRegisterServer2(TRUE);
}

STDAPI DllUnregisterServer() {
    return AMovieDllRegisterServer2(FALSE);
}

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

 エントリポイント以外の関数について公開するように設定します。モジュール定義ファイル「module.def」を新規作成します。

module.def
LIBRARY     WavWriter.dll

EXPORTS
DllGetClassObject       PRIVATE
DllCanUnloadNow         PRIVATE
DllRegisterServer       PRIVATE
DllUnregisterServer     PRIVATE

 DllGetClassObjectDllCanUnloadNowについては、DirectShowで実装されています。

 プロジェクトのプロパティを開き、「モジュール定義ファイル」に「module.def」を追加してください。

次のページ
作成したフィルタの使用

この記事は参考になりましたか?

Advanced/W-ZERO3 [es]で作ろう連載記事一覧

もっと読む

この記事の著者

syu5()

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/2055 2008/09/17 01:53

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー