DirectShowを使った自前ソースフィルタの実装 その4

ソースフィルタ編はこれが最後です。

前に実装していなかったIDataStreamをWinAPI系で実装してみたりフィルタを微妙な拡張をしてみたり、使用するコードを出してみたり。

 

追加するコードの宣言部

//アーカイブファイル読み込みクラス
class CFileStream : public IDataStream{
public:
	CFileStream(); //コンストラクタ
	virtual ~CFileStream(); //デストラクタ
	virtual HRESULT GetMediaType(CMediaType *lpMediaType,const AM_MEDIA_TYPE *lpDefaultType); //メディアの種類を取得する
	virtual HRESULT Seek(LONGLONG llPosition); //読み取り位置の移動
	virtual HRESULT Read(void *lpBuffer,DWORD dwToReadSize,DWORD *lpdwReadSize,BOOL bIsAligned); //現在の位置からデータを読み取る
	virtual BOOL GetSize(LONGLONG *lpllTotalSize,LONGLONG *lpllAvailableSize = NULL) const; //データのサイズを取得する
	virtual DWORD GetAlignment(void) const; //データにアライメントあわせの必要があるとき、そのサイズを返す
	virtual void Lock(void); //データを読み取るためにロックする
	virtual void Unlock(void); //データのロックを解除する
	BOOL SetFileHandle(const WCHAR *lpwFileName,HANDLE hFile); //ハンドルを設定する
private:
	HANDLE m_hFile; //ファイルハンドル
	WCHAR m_wszFileNameExt[16]; //ファイル拡張子
	CCriticalSection m_vReadCriticalSection; //読み込みクリティカルセクション
};
//データストリームを使ったロードクラス
class CFileStreamFilter : public CDataSourceReader, public IFileSourceFilter{
public:
	CFileStreamFilter(IUnknown *lpOwner,HRESULT *lphr); //コンストラクタ
	virtual ~CFileStreamFilter(); //デストラクタ
	DECLARE_IUNKNOWN;
	STDMETHODIMP NonDelegatingQueryInterface(REFIID refiid,void **lplpInterface); //QueryInterfaceの実装
	//IFileSourceFilterよりの継承
	STDMETHODIMP Load(LPCOLESTR lpwFileName,const AM_MEDIA_TYPE *lpMediaType); //ファイルを開く
	STDMETHODIMP GetCurFile(LPOLESTR *lplpwFileName,AM_MEDIA_TYPE *lpMediaType); //現在開いているファイル名を取得する
private:
	HANDLE m_hFile; //ファイルハンドル
	WCHAR *m_lpwFileName; //ファイル名
	CFileStream m_vFileStream; //データストリーム
};

フィルタについてはIFileSourceFilterを追加しています。実装しなくても実はいいのですが、これを実装しておくと

  • ファイルを開く関数を実装するのでそれを使うことができる
  • GraphEditなどでフィルタを見るとロードしたファイル名が表示される

という利点があるので上から実装しています。

 

追加実装コード

まずはCFileStreamから。

//拡張子とGUIDの関連づけ
typedef struct _ExtMediaType{
	WCHAR wszExtName[8]; //拡張子
	GUID guidMediaType; //メディアタイプ
} EXTMEDIATYPE;
//拡張子との関連づけ
static const EXTMEDIATYPE c_avExtMediaType[] = {
	{ L"mpg", MEDIASUBTYPE_MPEG1System },
	{ L"mpeg", MEDIASUBTYPE_MPEG1System },
	{ L"wma", MEDIASUBTYPE_Asf },
	{ L"wmv", MEDIASUBTYPE_Asf },
	{ L"avi", MEDIASUBTYPE_Avi },
};
//ファイルストリーム読み込み
//コンストラクタ
CFileStream::CFileStream() : m_hFile(NULL)
{
	memset(&m_wszFileNameExt,0x00,sizeof(m_wszFileNameExt));
}
//デストラクタ
CFileStream::~CFileStream()
{
}
//メディアの種類を取得する
HRESULT CFileStream::GetMediaType(CMediaType *lpMediaType,const AM_MEDIA_TYPE *lpDefaultType)
{
	const EXTMEDIATYPE *lpMedia; const WCHAR *lpwFileExt;
				
	if(lpDefaultType != NULL) return lpMediaType->Set(*lpDefaultType);
	lpMediaType->SetType(&MEDIATYPE_Stream);
	lpMediaType->SetSubtype(&MEDIASUBTYPE_NULL);
	lpMediaType->SetTemporalCompression(TRUE);
	lpMediaType->SetSampleSize(1);
	lpwFileExt = m_wszFileNameExt;
	if(*lpwFileExt == L'\0') return S_OK;
	for(lpMedia = c_avExtMediaType;lpMedia->wszExtName[0] != L'\0';lpMedia++){
		if(lstrcmpiW(lpMedia->wszExtName,lpwFileExt) == 0){
			lpMediaType->SetSubtype(&(lpMedia->guidMediaType));
			break;
		}
	}
	return S_OK;
}
//読み取り位置の移動
HRESULT CFileStream::Seek(LONGLONG llPosition)
{
	CheckPointer(m_hFile,E_FAIL);
	BOOL bRet = ::SetFilePointerEx(m_hFile,*(LARGE_INTEGER *)&llPosition,NULL,FILE_BEGIN);
	return bRet ? S_OK : E_FAIL;
}
//現在の位置からデータを読み取る
HRESULT CFileStream::Read(void *lpBuffer,DWORD dwToReadSize,DWORD *lpdwReadSize,BOOL bIsAligned)
{
	CheckPointer(m_hFile,E_FAIL);
	BOOL bRet = ::ReadFile(m_hFile,lpBuffer,dwToReadSize,lpdwReadSize,NULL);
	return bRet ? S_OK : E_FAIL;
}
//データのサイズを取得する
BOOL CFileStream::GetSize(LONGLONG *lpllTotalSize,LONGLONG *lpllAvailableSize) const
{
	if(lpllTotalSize == NULL || m_hFile == NULL) return FALSE;
	::GetFileSizeEx(m_hFile,(LARGE_INTEGER *)lpllTotalSize);
	if(lpllAvailableSize != NULL) *lpllAvailableSize = *lpllTotalSize;
	return TRUE;
}
//データにアライメントあわせの必要があるとき、そのサイズを返す
DWORD CFileStream::GetAlignment(void) const
{
	return 1;
}
//データを読み取るためにロックする
void CFileStream::Lock(void)
{
	m_vReadCriticalSection.Enter();
}
//データのロックを解除する
void CFileStream::Unlock(void)
{
	m_vReadCriticalSection.Leave();
}
//ハンドルを設定する
BOOL CFileStream::SetFileHandle(const WCHAR *lpwFileName,HANDLE hFile)
{
	int i;
	m_hFile = hFile;
	for(i = lstrlenW(lpwFileName) - 1;i >= 0;i--){ if(lpwFileName[i] == L'.'){ break; } }
	if(i > 0) lstrcpynW(m_wszFileNameExt,lpwFileName + i + 1,sizeof(m_wszFileNameExt) / sizeof(WCHAR));
	return TRUE;
}

宣言部で書いたとおり、LockやらUnlockは持っていますが実動作で非同期処理を意識する必要はないのでそのまま呼び出しています。

面倒なのがGetMediaTypeですね。できる限りsubtypeは設定して置いた方がいいので拡張子からGUIDを判別してそれを設定しています。

拡張子検知の方は今回はUNICODEなので普通に逆方向から調べたものを使っています。

 

で、拡張されたソースフィルタのコードはというと。

//ファイルストリームフィルタ
//コンストラクタ
CFileStreamFilter::CFileStreamFilter(IUnknown *lpOwner,HRESULT *lphr)
	: CDataSourceReader(NAME("FileStreamFilter"),lpOwner,lphr,&m_vFileStream), m_hFile(INVALID_HANDLE_VALUE), m_lpwFileName(NULL)
{
	if(lphr != NULL) *lphr = S_OK;
}
//デストラクタ
CFileStreamFilter::~CFileStreamFilter()
{
	if(m_lpwFileName != NULL){
		delete[] m_lpwFileName;
		m_lpwFileName = NULL;
	}
	if(m_hFile != INVALID_HANDLE_VALUE){
		::CloseHandle(m_hFile);
		m_hFile = INVALID_HANDLE_VALUE;
	}
}
//QueryInterfaceの実装
STDMETHODIMP CFileStreamFilter::NonDelegatingQueryInterface(REFIID refiid,void **lplpInterface)
{
	if(IsEqualIID(refiid,IID_IFileSourceFilter)) return GetInterface(static_cast(this),lplpInterface);
	else return CDataSourceReader::NonDelegatingQueryInterface(refiid,lplpInterface);
}
//ファイルを開く
STDMETHODIMP CFileStreamFilter::Load(LPCOLESTR lpwFileName,const AM_MEDIA_TYPE *lpMediaType)
{
	int nLength;
	CheckPointer(lpwFileName,E_POINTER);
	if(m_lpwFileName != NULL) return E_FAIL;
	
	nLength = lstrlenW(lpwFileName) + 2; m_lpwFileName = new WCHAR[nLength]; lstrcpynW(m_lpwFileName,lpwFileName,nLength);
	{
		CAutoLock lock(&m_vCSFilter);
		m_hFile = ::CreaetFileW(m_lpwFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
		if(m_hFile == INVALID_HANDLE_VALUE){ return E_INVALIDARG; }
		m_vFileStream.SetFileHandle(m_lpwFileName,m_hFile);
		m_vFileStream.GetMediaType(&m_vMediaType,lpMediaType);
		m_vMediaType = m_vMediaType;
	}
	return S_OK;
}
//現在開いているファイル名を取得する
STDMETHODIMP CFileStreamFilter::GetCurFile(LPOLESTR *lplpwFileName,AM_MEDIA_TYPE *lpMediaType)
{
	int nNameLength; LPOLESTR lpwFileName;
	
	CheckPointer(m_hFile,E_FAIL); CheckPointer(lplpwFileName,E_POINTER);
	nNameLength = lstrlenW(m_lpwFileName) + 1;
	lpwFileName = (LPOLESTR)CoTaskMemAlloc(nNameLength * sizeof(WCHAR));
	if(lpwFileName == NULL) return E_OUTOFMEMORY;
	memcpy(lpwFileName,m_lpwFileName,nNameLength * sizeof(WCHAR)); *lplpwFileName = lpwFileName;
	
	if(lpMediaType != NULL){
		HRESULT hr = CopyMediaType(lpMediaType,&m_vMediaType);
		if(hr != S_OK) return hr;
	}
	return S_OK;
}

IDataStreamをこのフィルタ内に所有してしまったわけですね。おかげで開く動作がコンストラクタ上でやりづらくなってしまっている、と。

あと、IFileSourceFilterを実装するためにQueryInterface、Load、GetCurFileをそれぞれ実装しています。

ファイル名として渡されるLPOLESTR(LPCOLESTR)は「メモリ確保時にCoTaskMemAllocを使っているUNICODE文字列」だと思えばいいです。

(constがつく場合は単にUNICODE文字列としていいのだが)

それさえわかっていれば動作は簡単ですね。stringを使っていないのでメモリを確保して・・・が面倒です。

 

そしてIGraphBuilderでロードするコードに一手間

かな~り古いですが、DirectShowを使う(1)より使用ポイントを言うなら

//ファイル名を指定してグラフを生成する
hr = m_lpGraphBuilder->RenderFile(lpwFileName,NULL);

の部分をこのように変更します。

do{
	CFileStreamFilter *lpStreamFilter; IBaseFilter *lpBaseFilter;
	lpStreamFilter = new CFileStreamFilter(NULL,&hr);
	if(FAILED(hr)){ delete lpStreamFilter; break; }
	hr = lpStreamFilter->QueryInterface(IID_IBaseFilter,&lpBaseFilter);
	if(FAILED(hr)){ delete lpStreamFilter; break; }
	hr = m_lpGraphBuilder->AddFilter(lpBaseFilter,L"File Stream Filter");
	if(FAILED(hr)){ delete lpStreamFilter; break; }
	hr = lpStreamFilter->Load(lpwFileName,NULL);
	if(FAILED(hr)) break;
	hr = m_lpGraphBuilder->Render(lpStreamFilter->GetPin(0));
} while(0);

手順としては

  1. CFileStreamFilterをクラスとして作成
  2. QueryInterfaceでIBaseFilterを取得
  3. IGraphBuilderにAddFilterでフィルタを追加
  4. IFileSourceFilterのLoadで読み込み
  5. IGraphBuilderのRenderで出力ピンから先をレンダリング

となります。RenderFileを使うときに比べると面倒ですね。

また、コード中でdeleteで解放しているパターンとしていないパターンが混じっていますが、これは参照状態の問題です。

newで作成された時点では参照カウントは0なので解放はReleaseではできません。

また、AddFilterが成功した時点では参照カウントは1となりまたGraphBuilderに参照されている状態となるため、GraphBuilderの解放とともに解放されるようになります。

そのため、失敗しても解放の指示をGraphBuilderに任せることになるからです。

 

というわけで一連の流れでした

見てわかるとおり自前の読み込み処理を提示するならMediaFoundationの方がDirectShowより簡単です。

それ以外はそうでもないのですが、ちょっとおもしろい現象でした。

その原因はDirectShowの動作構造が「フィルタ」と「ピン」で汎用的に書かれているためですね。

いろいろな面で使いやすいのは事実ですが。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL