WAV出力DirectShowフィルタの作成 2
フィルタクラスの作成
DirectShowフィルタはIBaseFilterインターフェースを継承したクラスとして実装します。しかし、これを直接継承して実装した場合、コード量が多くなるので、SDKが提供しているクラスライブラリを使います。
クラスライブラリにはフィルタの種類ごとに基底クラスが用意されていて、それらを派生させることにより独自のフィルタを容易に実装できます。今回作成するフィルタはオーディオのレンダラフィルタなので、CBaseRendererを派生させてCWavWriterクラスを作成します。クラス図を図1に示します。

まずはメンバ変数を定義します。必要なメンバ変数としては、WAVファイルのフォーマット情報、ファイルハンドル、クリティカルセクションのオブジェクトです。
WAVファイルのフォーマット情報はWAVEFORMATEX構造体へのポインタとします。これは、フォーマットによって構造体のサイズが一定ではないため動的に確保する必要があるためです。
クリティカルセクションのオブジェクトはファイル出力処理をマルチスレッドで実行したときに不整合を起こさないために使います。ここではクラスライブラリで提供されているCCritSecクラスを使います。このクラスはCAutoLockクラスと組み合わせると、スコープを抜けた時点で自動的にクリティカルセクションを抜けるため便利です。
フィルタクラスのメソッドは、アプリケーションスレッド、ストリーミングスレッド、または、その両方から呼ばれるものがあります。
ファイルを出力するフィルタの場合、ストリーミングスレッド側でファイルを出力中に、アプリケーションスレッド側でフィルタグラフを停止した場合、出力完了までファイルを閉じることのないように排他制御しなければなりません。
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しています。
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インターフェースで定義されているメソッドです。
#ifdef DEBUG STDMETHODIMP_(ULONG) NonDelegatingRelease() { return CUnknown::NonDelegatingRelease(); } #endif
いよいよフィルタのコアとなる部分を作りこんでいきます。フィルタとして動作するためのほとんどのメソッドがCBaseRendererで実装済みですが、CheckMediaTypeとDoRenderSampleは純粋仮想関数になっているため実装が必要です。ここでは、これら2つに加えOnStartStreamingとOnStopStreamingも実装します。
CheckMediaTypeでは、上流フィルタの出力ピンから接続されようとするときに呼び出されます。ここで行うべき処理は、メソッド名のとおりメディアタイプをチェックします。CMediaTypeとして渡されるメディアタイプをチェックし、受け入れ可能であればS_OK、そうでなければS_FALSEを返します。
もし受け入れ可能である場合、フォーマット情報をWAVEFORMATEX構造体として取得します。
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を使って音声データをファイルへ出力します。
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ファイルのヘッダを出力します。ただしデータサイズは開始時点で決まっていないのでダミーで埋めます。
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メソッドは、フィルタの状態が停止に遷移したときに呼び出されます。ここではヘッダにデータサイズなどを書き込んで、ファイルを閉じます。
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を特定するためにレジストリが参照されます。
実装手順を以下に示します。
- CFactoryTemplateクラスのグローバル変数をg_Templatesという名前で宣言しフィルタ情報で初期化する(表4)
- int g_cTemplatesを宣言し、フィルタ情報の数で初期化する。
- レジストリへ登録するDllRegisterServer関数と、解除するDllUnregisterServer関数を定義する。
- DllMain関数を定義し、DllEntryPoint関数を呼び出す。
| m_Name | フィルタ名 |
| m_ClsID | クラスIDへのポインタ |
| m_lpfnNew | インスタンスを作成する関数へのポインタ |
| m_lpfnInit | DLLエントリポイントから呼び出される関数へのポインタ(今回は未使用) |
| m_pAMovieSetup_Filter | AMOVIESETUP_FILTER構造体へのポインタ |
レジストリへ登録および解除はAMovieDllRegisterServer2を使うと簡単にできます。
#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」を新規作成します。
LIBRARY WavWriter.dll EXPORTS DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE
DllGetClassObjectとDllCanUnloadNowについては、DirectShowで実装されています。
プロジェクトのプロパティを開き、「モジュール定義ファイル」に「module.def」を追加してください。
