Microsoft DirectX 8.0 |
このトピックでは、Microsoft® DirectShow® を使ってデジタル ビデオ (DV) カムコーダを制御する方法を説明する。以下のセクションで構成される。
DirectShow での DV の全般的な情報については、「DirectShow でのデジタル ビデオ」を参照すること。
WDM ビデオ キャプチャ フィルタは、カムコーダの制御用に以下の 3 つのインターフェイスを公開している。
IAMExtDevice | 外部デバイス制御用の基本インターフェイス。 |
IAMExtTransport | VCR 機能を制御する。 |
IAMTimeCodeReader | デバイスからタイムコードを読み取る。 |
キャプチャ デバイスを選択し、キャプチャ フィルタのインスタンスを生成した後、フィルタにこれらのインターフェイスの有無を問い合わせる。以下の例では、インターフェイス ポインタと共に、各インターフェイスの利用可能性を示すブール値を保持するカスタム構造体を宣言している。
struct _MyDevCap { IAMExtDevice *pDevice; IAMExtTransport *pTransport; IAMTimecodeReader *pTimecode; BOOL bHasDevice; BOOL bHasTransport; BOOL bHasTimecode; } MyDevCap; HRESULT hr; IBaseFilter *pDVCam; // キャプチャ フィルタへのポインタ。 // キャプチャ フィルタのインスタンスを生成する (表示されない)。 hr = pDVCam->QueryInterface(IID_IAMExtDevice, (void **)&MyDevCap.pDevice); MyDevCap.bHasDevice = (SUCCEEDED(hr)); hr = pDVCam->QueryInterface(IID_IAMExtTransport, (void **)&MyDevCap.pTransport); MyDevCap.bHasTransport = (SUCCEEDED(hr)); hr = pDVCam->QueryInterface(IID_IAMTimecodeReader, (void **)&MyDevCap.pTimecode); MyDevCap.bHasTimecode = (SUCCEEDED(hr));
注 : DV カムコーダ ドライバの Msdv.sys と共にこれらのインターフェイスを使用する場合は、ヘッダー ファイル XPrtDefs.h をプロジェクトに含める。このファイルは、msdn.microsoft.com/library/techart/dvsupport.htm から入手できる。
デバイス コントロール インターフェイスを使用すると、デバイスの状態と能力を問い合わせることができる。このセクションでは、いくつかの取得可能で有益な項目について説明する。詳細については、インターフェイスのリファレンスを参照すること。
キャプチャ デバイスに関する情報で最も重要なものは、デバイスのタイプである。デバイスが VCR 機能を持っているのか、単なるカメラなのかといった情報だ。デバイス タイプを取得するには、値 ED_DEVCAP_DEVICE_TYPE を使って IAMExtDevice::GetCapability メソッドを呼び出す。このメソッドが値 ED_DEVTYPE_VCR を返した場合、デバイスは VCR であり、ポーズ、停止、早送り、巻き戻しなどの機能を持っている。以下のコードに、デバイス タイプを問い合わせる方法を示す。
if (MyDevCap.bHasDevice) { LONG lDeviceType = 0; MyDevCap.pDevice->GetCapability(ED_DEVCAP_DEVICE_TYPE, &lDeviceType, 0); if (lDeviceType == ED_DEVTYPE_VCR) { // デバイスは VCR である。すべての VCR 機能を有効にする。 } else { // デバイスはカメラである。 // 録画と録画ポーズを有効にし、その他の機能は無効にする。 } }
注 : 通常、カムコーダは VCR 機能とカメラ機能の間で切り替えることができ、それに応じたデバイス タイプを返す。カムコーダがオフラインになった場合、次に使用可能になったときにもう一度問い合わせる必要がある。デバイスが削除されると、フィルタ グラフ マネージャが EC_DEVICE_LOST イベントを送信する。詳細については、「キャプチャ アプリケーションの書き方」を参照すること。
再生、ポーズ、停止など、デバイスの現在の状態を取得するには、IAMExtTransport::get_Mode メソッドを呼び出す。このメソッドは、デバイスの状態を示す以下のような定数を取得する。
値 | デバイスの状態 |
---|---|
ED_MODE_PLAY | 再生 |
ED_MODE_STOP | 停止 |
ED_MODE_FREEZE | ポーズ |
ED_MODE_FF | 早送り |
ED_MODE_REW | 巻き戻し |
ED_MODE_RECORD | 録画 |
ED_MODE_RECORD_FREEZE | 録画ポーズ |
DV テープが再生中、または録画ポーズ モードの間は、IAMTimecodeReader::GetTimecode メソッドを呼び出して、SMPTE タイムコードまたは絶対トラック番号を取得することができる。このメソッドは、タイムコードを記述する TIMECODE_SAMPLE 構造体へのポインタを受け取る。このメソッドを呼び出す前に、構造体の dwFlags メンバを初期化する。タイムコードを取得する場合は値 ED_DEVCAP_TIMECODE_READ を使用し、絶対トラック番号を取得する場合は値 ED_DEVCAP_ATN_READ を使用する。
TIMECODE_SAMPLE 構造体の timecode メンバが、TIMECODE 構造体である。メソッドが戻ると、TIMECODE 構造体の dwFrames メンバにタイムコードまたはトラック番号が格納されている。タイムコードの場合は、時間、分、秒、およびフレームが、hhmmssff のフォーマットで 2 進化 10 進法 (BCD) の値として DWORD にパックされている。ビットマスクを使ってそれぞれの値を取り出す。
以下の例では、タイムコードとトラック番号を取り出している。情報を表示する関数 DisplayTimecode と DisplayTrackNum は、アプリケーションで定義されているものとする。
if (MyDevCap.bHasTimecode) { TIMECODE_SAMPLE TimecodeSample; TimecodeSample.timecode.dwFrames = 0; char szBuf[32]; TimecodeSample.dwFlags = ED_DEVCAP_TIMECODE_READ; if (hr = MyDevCap.pTimecode->GetTimecode(&TimecodeSample), SUCCEEDED(hr)) { wsprintf(szBuf, "TIMECODE %.2x : %.2x : %.2x : %.2x", (TimecodeSample.timecode.dwFrames & 0xFF000000) >> 24, (TimecodeSample.timecode.dwFrames & 0x00FF0000) >> 16, (TimecodeSample.timecode.dwFrames & 0x0000FF00) >> 8, (TimecodeSample.timecode.dwFrames & 0x000000FF)); DisplayTimecode(szBuf); } TimecodeSample.dwFlags = ED_DEVCAP_ATN_READ; if (hr = MyDevCap.pTimecode->GetTimecode(&TimecodeSample), SUCCEEDED(hr)) { wsprintf(szBuf, "%d", TimecodeSample.timecode.dwFrames); DisplayTrackNum(szBuf); } }
デバイスを制御するには、IAMExtTransport::put_Mode メソッドを呼び出し、前のセクションで示した定数の 1 つを使って新しい状態を指定する。たとえば、デバイスを停止するには、以下のコードを使用する。
MyDevCap.pTransport->put_Mode(ED_MODE_STOP);
ただし、カムコーダは物理デバイスなので、コマンドを発行してからそれが完了するまでにある程度時間がかかる。そのため、アプリケーションはコマンドの完了を待つ二次スレッドを作成しておく必要がある。コマンドが完了すると、そのスレッドがユーザー インターフェイスを更新できる。状態変化が順番に行われるように、クリティカル セクションを使用する。
カムコーダの中には、デバイスの状態が変化したときにアプリケーションに通知できるものがある。デバイスがこの機能をサポートしている場合、二次スレッドはその通知を待つことができる。サポートしていない場合、アプリケーションはデバイスを定期的にポーリングしなければならない。
最初に、クリティカル セクションの変数、イベント、およびスレッド ハンドルを宣言する。クリティカル セクションは、デバイスの状態を同期するために使用する。イベントは、アプリケーションが終了したときに二次スレッドを終了させるために使用する。
CRITICAL_SECTION csIssueCmd; HANDLE hThreadEndEvent = CreateEvent(NULL, TRUE, FALSE, NULL); HANDLE hThread = 0;
キャプチャ フィルタのインスタンスを生成した後、二次スレッドを作成する。
DWORD ThreadId; hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &ThreadId);
二次スレッドの中で、IAMExtTransport::GetStatus メソッドを 2 回呼び出す。最初の呼び出しでは、値 ED_NOTIFY_HEVENT_GET を渡し、処理の完了時に通知済状態になるイベントのハンドルを取得する。
HANDLE hEvent = NULL; hr = MyDevCap.pTransport->GetStatus(ED_NOTIFY_HEVENT_GET, (long *)&hEvent);
2 回目の呼び出しでは、デバイスの状態を受け取る変数へのポインタと共に、値 ED_MODE_CHANGE_NOTIFY を渡す。
LONG State; hr = MyDevCap.pTransport->GetStatus(ED_MODE_CHANGE_NOTIFY, &State);
デバイスが通知をサポートしている場合、この呼び出しの戻り値は E_PENDING である。次に、イベントが通知済状態になるのを待つ。次の処理が完了すると、イベントが通知済状態になり、State の値でデバイスの新しい状態が指定される。ここで、新しい状態を反映するようにユーザー インターフェイスを更新できる。
以下のコードに、完全なスレッドの処理を示す。ユーザー インターフェイスを更新する UpdateTransportState 関数は、アプリケーションで定義されているものとする。スレッドは 2 つのイベントを待つことに注意してほしい。通知イベント (hEvent) と、スレッド終了イベント (hThreadEndEvent) である。また、デバイス状態の変数を保護するために、クリティカル セクションを使用している部分にも注意すること。
DWORD WINAPI ThreadProc(void *pParam) { HRESULT hr; HANDLE EventHandles[2]; HANDLE hEvent = NULL; DWORD WaitStatus; LONG State; // 次の処理が完了したときに通知済状態になるイベントを取得する。 hr = MyDevCap.pTransport->GetStatus(ED_NOTIFY_HEVENT_GET, (long *) &hEvent); while (hThread && hEvent && hThreadEndEvent) { EnterCriticalSection(&csIssueCmd); State = 0; hr = MyDevCap.pTransport->GetStatus(ED_MODE_CHANGE_NOTIFY, &State); LeaveCriticalSection(&csIssueCmd); if(hr == E_PENDING) { // デバイスは通知をサポートしている。 EventHandles[0] = hEvent; EventHandles[1] = hThreadEndEvent; WaitStatus = WaitForMultipleObjects(2, EventHandles, FALSE, INFINITE); if(WAIT_OBJECT_0 == WaitStatus) { UpdateTransportState(State); } else { break; // このスレッドを終了する。 } } else { break; // このスレッドを終了する (デバイスは通知をサポートしていない)。 } } // while // 通知をキャンセルする。 hr = MyDevCap.pTransport->GetStatus(ED_NOTIFY_HEVENT_RELEASE, (long *) &hEvent); return 1; }
以下のように、デバイスにコマンドを発行するときにもクリティカル セクションを使用する。
// "停止" コマンドを発行する。 EnterCriticalSection(&csIssueCmd); if(hr = MyDevCap.pTransport->put_Mode(ED_MODE_STOP), SUCCEEDED(hr)) UpdateTransportState(ED_MODE_STOP); LeaveCriticalSection(&csIssueCmd);
アプリケーションが終了する前に、二次スレッドを終了させる。
if (hThread) { // このイベントが通知済状態になると、スレッドが終了する。 if (SetEvent(ThreadEndEvent)) { // スレッドが終了するのを待つ。 WaitForSingleObjectEx(hThread, INFINITE, FALSE); } CloseHandle(hThreadEndEvent); CloseHandle(hThread);
デバイスが通知をサポートしていない場合、値 ED_MODE_CHANGE_NOTIFY で IAMExtTransport::GetStatus を呼び出すと、E_PENDING 以外の値が返される。その場合は、デバイスをポーリングして状態を判定しなければならない。
ここでも、前に説明したように二次スレッドとクリティカル セクションを使用するべきである。このスレッドは、一定間隔でデバイスに状態を問い合わせる。以下の例に、スレッドの実装法の 1 つを示す。
DWORD WINAPI ThreadProc(void *pParam) { HRESULT hr; LONG State; DWORD WaitStatus; while (hThread && hThreadEndEvent) { EnterCriticalSection(&csIssueCmd); State = 0; hr = MyDevCap.pTransport->get_Mode(&State); LeaveCriticalSection(&csIssueCmd); UpdateTransportState(State); // しばらくの間、またはスレッドが終了するまで待つ。 WaitStatus = WaitForSingleObjectEx(hThreadEndEvent, 200, FALSE); if (WaitStatus == WAIT_OBJECT_0) break; // ここでスレッドを終了する。 // そうしなければ、待機はタイムアウトになる。もう一度、ポーリングを行う。 } return 1; }
デバイス コントロール インターフェイスを使って、DV テープからコンピュータ ファイルへ送信したり、ファイルからテープへ送信したりする。
次の図は、ファイルからテープへ送信する場合のフィルタ グラフを示す。
『Digital Video Camcorder Support in Windows 98 and Windows 2000』 (www.microsoft.com/hwdev/vidcap/DVCam.htm) は、DV カムコーダの制御を解説したホワイト ペーパーである。