Date

Windows XP SP1 以上に搭載されている ストリームバッファエンジン Stream Buffer Engine を使って、DV カメラのライブ映像を表示し、表示中に過去にさかのぼったり再生速度を変えてみたりするアプリケーションを作成する。

ストリームバッファエンジンとは

ストリームバッファエンジンとは、2 つのフィルタグラフを使ってライブ映像のタイムシフト・早送り・巻き戻しを実現する仕組みである。

2 つのフィルタグラフのうち、1 つはシンクグラフ (sink graph)と呼ばれ、ライブ映像をディスクへ書き込むためのグラフである。もう一方はソースグラフで、シンクグラフによってディスクへ書き込まれたデータを元に再生するグラフである。

シンクグラフによって書き込まれたファイルのことを一時バッキングファイルと呼ぶ。バッキングファイルは複数個存在することがある。さらにバッキングファイルを参照するスタブファイルがある。

ストリームバッファエンジンは、MPEG-2 または DV のみ対応する。AVI など他のコンテナや、MPEG-2, DV 以外で圧縮されている動画は扱えない。もしアナログビデオキャプチャを使いたい場合は、一度 DV または MPEG-2 に圧縮してから使う。また DV でも SD のみで HDV などは扱えない。

実装

プロジェクトを新規作成し、stdafx.h で sbe.h を include する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#pragma once
#define WIN32_LEAN_AND_MEAN
#define _WIN32_DCOM
#pragma comment(lib, "strmiids.lib")
#pragma comment(lib, "quartz.lib")
#pragma comment(lib, "ole32.lib")
#include <atlbase.h>
#include <conio.h>
#include <tchar.h>
#include <windows.h>
#include <objbase.h>
#include <dshow.h>
#include <sbe.h>

StreamBufferEngineTest.cpp を新規作成する。以下、ここに実装していく。

DVカメラデバイスを列挙する関数

DVカメラデバイスを列挙する関数CreateDvCamの実装については割愛する。詳しくはDV カメラ映像を表示のページを参照すること。

レジストリの用意

Windows XPでは不要だが、Vistaではバッキングファイルの設定を記録するレジストリを用意しなければならない。

RegCreateKeyで設定を記録するレジストリを作成したら、ストリーミングバッファエンジンの初期化インターフェイスIStreamBufferInitializeに、そのレジストリハンドルを渡す。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
long lRes = RegCreateKey(HKEY_CURRENT_USER, TEXT("SOFTWARE\\MyStreamBufferKey"), &hkey);
CComPtr<IStreamBufferInitialize> sink_init;
CComPtr<IStreamBufferConfigure3> sink_cfg;
hr=sink_cfg.CoCreateInstance(CLSID_StreamBufferConfig);
hr=sink_cfg->QueryInterface(IID_IStreamBufferInitialize,(void**)&sink_init);
hr=sink_init->SetHKEY(hkey);
RegCloseKey(hkey);
hr=sink_cfg->SetDirectory(_T("C:\\Users\\Public\\Recorded TV\\"));
hr=sink_cfg->SetBackingFileCount(6, 8);
hr=sink_cfg->SetBackingFileDuration(60 * 4);
hr=sink_cfg->SetFFTransitionRates(8, 32);
hr=sink_cfg->SetNamespace(NULL);

シンクグラフの作成

まずフィルタグラフを作成し、入力となるDVカメラの入力フィルタを追加する。そして、ストリームバッファシンクフィルタを追加し、接続する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// >> Sink graph
sink_cap_grph.CoCreateInstance(CLSID_CaptureGraphBuilder2);
sink_graph.CoCreateInstance(CLSID_FilterGraph);
sink_cap_grph->SetFiltergraph(sink_graph);
sink_graph->QueryInterface(IID_IMediaControl, (void**)&sink_ctrl);
// add filter : DV cam
CreateDvCam(dv_cam);
sink_graph->AddFilter(dv_cam,TEXT("DVCam"));
// add filter : DV splitter
dv_splitter.CoCreateInstance(CLSID_DVSplitter);
sink_graph->AddFilter(dv_splitter,TEXT("Splitter"));
// add filter : SBE sink filter
sink_filter.CoCreateInstance(CLSID_StreamBufferSink);
sink_graph->AddFilter(sink_filter,TEXT("SBE sink"));
// connect Sink graph.
hr=sink_cap_grph->RenderStream(&PIN_CATEGORY_CAPTURE ,&MEDIATYPE_Interleaved,dv_cam,NULL,dv_splitter);
hr=sink_cap_grph->RenderStream(NULL,&MEDIATYPE_Video,dv_splitter,NULL,sink_filter);
hr=sink_cap_grph->RenderStream(NULL,&MEDIATYPE_Audio,dv_splitter,NULL,sink_filter);

接続したら、作成したレジストリキーをRegOpenKeyで開き、ストリームバッファシンクに渡す。

1
2
3
4
5
6
long lRes = RegOpenKey(HKEY_CURRENT_USER, TEXT("SOFTWARE\\MyStreamBufferKey"), &hkey);
CComPtr<IStreamBufferInitialize > sink_init;
hr=sink_filter->QueryInterface(IID_IStreamBufferInitialize,(void**)&sink_init);
hr=sink_init->SetHKEY(hkey);
hr=sink_filter->QueryInterface(IID_IStreamBufferSink,(void**)&sbe_sink);
hr=sbe_sink->LockProfile(NULL);

ソースグラフの作成

パッキングファイルから読み取って再生するソースグラフを作成する。

ストリームバッファソースフィルタCLSID_StreamBufferSourceを作成しフィルタグラフへ追加する。タイムシフトや再生速度を変えたりするためにIStreamBufferMediaSeekingを取得する。

1
2
3
4
5
6
7
8
hr=src_graph.CoCreateInstance(CLSID_FilterGraph);
hr=src_graph->QueryInterface(IID_IMediaControl, (void**)&src_ctrl);
// add filter : SBE source filter
hr=src_filter.CoCreateInstance(CLSID_StreamBufferSource);
hr=src_graph->AddFilter(src_filter,TEXT("SBE source"));
hr=src_filter->QueryInterface(IID_IStreamBufferSource, (void**)&sbe_source);
hr=sbe_source->SetStreamSink(sbe_sink);
hr=sbe_source->QueryInterface(IID_IStreamBufferMediaSeeking, (void**)&sb_seeking);

ストリームバッファソースフィルタの各出力ピンを列挙しIGraphBuilder::Renderメソッドを呼び出すことによりデフォルトのレンダラーでレンダリングするようにフィルタグラフを作成する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
CComPtr<IEnumPins> pE;
src_filter->EnumPins(&pE);
while(true) {
    CComPtr<IPin> out_pin;
    hr=pE->Next(1,&out_pin,0);
    if(hr!=S_OK) {
        HrToStrByAMGet(hr);
        break;
    }
    src_graph->Render(out_pin);
}

フィルタグラフの制御

シンクグラフとソースグラフ両方を開始する。

キーボード入力を待ち、入力内容によって再生制御する。再生の制御は、IstreamBufferMediaSeekingで行う。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
src_ctrl->Run();
sink_ctrl->Run();
int ch;
printf("[P]revious [F]orward [S]peed Up(2.0x) [D]own(0.5x)"
    "[N]ormal speed [R]everse [spc] Reset\n");
do{
    LONGLONG earliest, latest;
    LONGLONG current, stop;
    const LONGLONG skipTime=100000000ll;
    ch=_getch();
    sb_seeking->GetAvailable(&earliest, &latest);
    sb_seeking->GetPositions(&current, &stop);
    printf("%I64d %I64d %I64d %I64d\n",earliest,latest,current,stop);
    switch(ch){
        case ' ':
            sb_seeking->SetPositions
                (&latest,AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning);
            sb_seeking->SetRate(1.0f);
            break;
        case 'P':
            if((current-skipTime)>=earliest){
                current-=skipTime;
            }else{
                current=earliest;
            }
            sb_seeking->SetPositions
                (&current,AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning);
            break;
        case 'F':
            if((current+skipTime)<=latest){
                current+=skipTime;
            }else{
                current=latest;
            }
            sb_seeking->SetPositions
                (&current,AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning);
            break;
        case 'S':
            sb_seeking->SetRate(2.0f);
            break;
        case 'D':
            sb_seeking->SetRate(0.5f);
            break;
        case 'N':
            sb_seeking->SetRate(1.0f);
            break;
        case 'R':
            sb_seeking->SetRate(-1.0f);
            break;
    }
} while(ch!=0x0d);
src_ctrl->Stop();
sink_ctrl->Stop();

シーク処理

シーク処理はIstreamBufferMediaSeeking::SetPositionsメソッドでおこなっているが、その前にシーク可能範囲と位置を取得するためGetAvailableGetPositionsメソッドを呼んでいる。

1
2
sb_seeking->GetAvailable(&earliest, &latest);
sb_seeking->GetPositions(&current, &stop);

この 2 つのメソッドで以下の値が取得できる。

earliest 最もさかのぼって再生できる位置。 0 であれば、録画を開始した時点までシークできる。
latest 録画経過時間。この時間より後ろへはシークできない。
current 現在の再生位置
stop 再生停止位置 (この位置にくると再生が停止する。初期値は大きな値 (`MAXLONGLONG`) が設定されていて実際のところは未使用)

この 4 つの値を取得すれば SetPositions を使ってシーク可能範囲が定まる。

動作確認

実際に動かす前に、DV カメラをカメラモードにして電源を入れて IEEE1394 で接続しておく。

OS に認識された状態になったら作成したアプリケーションを実行する。

画面に DV カメラ映像が表示されたらキーを入力する。

1
2
3
4
5
6
7
8
[P] 10秒前へ戻る
[F] 10秒後へ進む
[S] 2倍速で再生する
[D] 0.5倍速で再生する (スロー再生)
[N] 1倍速で再生する
[R] -1倍速で再生する (逆再生)
[スペース] 元に戻る
[Enter] 終了

DV カメラをもってない場合は、アナログビデオキャプチャに置き換えてみるのも良いだろう。その場、DV エンコーダフィルタをストリームバッファシンクフィルタの前に入れて映像を DV 形式に圧縮しておくこと。

https://github.com/mahorigahama/StreamBufferEngine


Comments

comments powered by Disqus