まだこの話題を続けます。なんかここまで書くとどこかの雑誌などでちゃんとした連載記事を書きたくなるのは気のせいでしょうか・・・。
で、やりやすい話題として持ってきました。IMFByteStreamを実装してみます。
そもそもIMFByteStreamとは
まず実装対象となっているIMFByteStreamについて確認しておきましょう。
IMFByteStreamとは、MediaFoundationでバイト単位のデータを扱うときに使用するインターフェイスです。
一応読み込み、書き込みの両方をサポートしており、非同期読み取り、同期読み取りの両方をサポートしています。
DirectShowのIAsyncReaderに書き込みのサポートを追加したもの、ともいえます。
ちょっと非同期処理が甘いような気がする
IAsyncReaderに比べて非同期の扱いがかなり甘いと思います。
というのも、非同期読み取りの開始を行うときに開始バイト位置を指定しません。
位置は(読み取り開始の前に位置決めがない場合)前回の処理が完了した時点から、となっています。
本当の非同期ならどのタイミングで読まれるかは不明なのですが、これに関しては「処理は非同期処理を発行した順番に行われる」が守られていることを前提としているようです。
ちなみに、MediaFoundationの非同期処理はどのインターフェイスでもだいたいこんな流れになっています。
- 対象のインターフェイスに非同期処理を依頼する(Begin~)。このとき、IMFAsyncCallbackを渡して処理完了後にコールバックしてもらう
- 非同期処理をインターフェイス内で行う。この処理は依頼された順序を守って行うこと。(順序を変更してはならない)
- 非同期処理が完了した後、依頼時に渡されたIMFAsyncCallbackにあるInvokeを呼び出して終了したことを通知する。このとき、非同期処理を行った側はIMFAsyncResultを内部で生成してそれをInvokeで渡す必要がある。ただし、Invokeはほとんどの場合MediaFoundationが持つ非同期処理キュー上から発行する必要がある
- IMFAsyncCallback側は非同期処理が完了したことを検知するとその結果を保存する。その後、非同期処理完了を確認したことを依頼先に通知する(End~)
- 非同期処理側は終了確認通知時(End~呼び出し時)に終了時に必要な情報を引き渡す。これで処理が完全に完了する
というわけで定義
今回の実装ではWinAPIの各処理(CreateFile,ReadFile,SetFilePointerEx,CloseHandle)を使った実装を行います。
自前のアーカイブなどからデータを読み取るときにはそれぞれの部分を置き換えるように実装すると良いと思います。
#ifndef __mfbytestream_h__ #define __mfbytestream_h__ class CMFByteStream : public CUnknownBase, public IMFByteStream{ public: CMFByteStream(HRESULT *lphr,const TCHAR *lpFileName); //コンストラクタ virtual ~CMFByteStream(); //デストラクタ //IUnknownの機能を実装する DECLARE_IUNKNOWN; STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,void **ppv); //インターフェイスの要求 //IMFByteStreamの実装 STDMETHODIMP GetCapabilities(DWORD *pdwCapabilities); //ストリームの能力を取得する STDMETHODIMP GetLength(QWORD *pqwLength); //ストリームのサイズを取得する STDMETHODIMP SetLength(QWORD qwLength); //ストリームのサイズを設定する STDMETHODIMP GetCurrentPosition(QWORD *pqwPosition); //ストリームの位置を取得する STDMETHODIMP SetCurrentPosition(QWORD qwPosition); //ストリームの位置を設定する STDMETHODIMP IsEndOfStream(BOOL *pfEndOfStream); //ストリームの終端かどうか STDMETHODIMP Read(BYTE *pb,ULONG cb,ULONG *pcbRead); //同期読み取りを行う STDMETHODIMP BeginRead(BYTE *pb,ULONG cb,IMFAsyncCallback *pCallback,IUnknown *punkState); //非同期読み取りを開始する STDMETHODIMP EndRead(IMFAsyncResult *pResult,ULONG *pcbRead); //非同期読み取りを終了する STDMETHODIMP Write(const BYTE *pb,ULONG cb,ULONG *pcbWritten); //同期書き込みを行う STDMETHODIMP BeginWrite(const BYTE *pb,ULONG cb,IMFAsyncCallback *pCallback,IUnknown *punkState); //非同期書き込みを開始する STDMETHODIMP EndWrite(IMFAsyncResult *pResult,ULONG *pcbWritten); //非同期書き込みを終了する STDMETHODIMP Seek(MFBYTESTREAM_SEEK_ORIGIN SeekOrigin,LONGLONG llSeekOffset,DWORD dwSeekFlags,QWORD *pqwCurrentPosition); //ストリーム位置を移動する STDMETHODIMP Flush(void); //書き込みバッファのフラッシュを行う STDMETHODIMP Close(void); //ストリームを閉じる HRESULT Open(void); //ストリームを開く protected: //内部ロックオブジェクト class CLockStream{ public: CLockStream(CMFByteStream *obj) : m_obj(obj){ ::EnterCriticalSection(&obj->m_vCSStream); } ~CLockStream() { ::LeaveCriticalSection(&m_obj->m_vCSStream); } private: CMFByteStream *m_obj; }; class CLockQueue{ public: CLockQueue(CMFByteStream *obj) : m_obj(obj){ ::EnterCriticalSection(&obj->m_vCSQueue); } ~CLockQueue() { ::LeaveCriticalSection(&m_obj->m_vCSQueue); } private: CMFByteStream *m_obj; }; //非同期処理アイテムの定義 typedef struct _AsyncItem{ IMFAsyncResult *lpResult; BYTE *pbBuffer; ULONG cbSize,cbRead; uint64_t nReadPos; } ASYNCITEM; typedef std::list<ASYNCITEM> dequeAsyncItem; HRESULT InnerRead(BYTE *pb,ULONG cb,ULONG *pcbRead,uint64_t nReadPos); //内部読み込み処理 static unsigned int __stdcall AsyncThread(void *lpContext); //非同期処理スレッド inline bool IsValidStream(void) const { return m_hFileStream != NULL; } //ストリームが有効かどうか inline bool IsValidThread(void) const { return m_hAsyncThread != NULL; } //スレッドが有効かどうか private: TCHAR *m_lpFileName; //ファイル名 HANDLE m_hFileStream; //ファイルハンドル uint64_t m_ullStreamPos; //ストリーム位置 uint64_t m_ullStreamSize; //ストリームサイズ CRITICAL_SECTION m_vCSStream; //ストリーム同期ハンドル HANDLE m_hAsyncThread; //非同期処理スレッド HANDLE m_hThreadEvent; //スレッドイベント CRITICAL_SECTION m_vCSQueue; //キューアイテム同期ハンドル dequeAsyncItem m_dequeWorkItem; //未処理アイテムキュー dequeAsyncItem m_dequeDoneItem; //処理済みアイテムキュー IMFAttributes *m_lpAttributes; //属性値管理 }; #endif //__mfbytestream_h__
ちなみに非同期アイテムを管理しているキューはdequeではなくlistを使っています。
実装を始めたときには実はdequeだったのですが、これだととある時に処理速度が問題になる可能性に至りlistを使っています。
動作テストをしたときにはdequeでも大丈夫だろう、という結論はあるのですがそのままにしてあります。
後、書き込み系については実装を行いません。前にIStreamの実装でWrite部を実装しなかったように今回も実装する必要はありません。
書き込み部の実装が必要になるとしたらせいぜいメディアキャプチャを使うときくらいだと思います。
実装部はちょっと大きめ
いろいろと実装するので説明が大変です。IStreamの時とほぼ同じですが同期処理を行う必要があるので書くことが多いです。