Date

RenderFileRenderStreamを使ってフィルタグラフを構築するとき、必要に応じて中間フィルタやレンダラーフィルタが自動的に挿入される(インテリジェント接続)。これによりフィルタグラフを構築するコードが少なく済む。

この記事では、インテリジェント接続に頼らずに一つ一つフィルタを接続しWMVファイルの再生を行う。少し低レベルの処理を覗いてみればDirectShowに対する理解が深まるだろう。 ## お手本となるフィルタグラフを確認する

まずはお手本となるフィルタグラフをGraphEditで確認してみる。

GraphEditでメニュー [File]-[Render Media File...] を選択「C:\Users\Public\Videos\Sample Videos\Bear.wmv」を開いてみよう。

名前 DMO or DirectShowフィルタ
WM ASF リーダー DirectShowフィルタ
WMAudio Decoder DMO DMO
WMVideo Decoder DMO DMO
DirectSound レンダラ DirectShowフィルタ
Video Mixing Renderer(VMR) DirectShowフィルタ

水色のボックスはDirectShowフィルタ、緑のボックスはDMO(DirectX Media Object)と呼ばれるオブジェクトである。OSで提供されている一部のコーデックはDirectShowフィルタではなくDMOとして提供されている。DMOはDirectShowフィルタとはアーキテクチャが異なるものですが、ラッパーが用意されていてDirectShowフィルタとして扱うことができる。

GraphEditに表示されたWM ASF リーダーフィルタの形状を見るとビデオとオーディオの2つの出力があることが分かる。そこからそれぞれデコーダのDMOへ接続している。最後はレンダラフィルタでレンダリングが行われる。

フィルタグラフの構築を実装する

ソースフィルタを追加する

ソースフィルタはIGraphBuilder::AddSourceFilterというメソッドが特別に用意されているので、それを使う。AddSourceFilterを使うと、インスタンスの生成、フィルタグラフへの追加、ファイル名の指定まで一通りやってくれるので便利である。

1
2
3
4
5
6
7
8
CComPtr<IGraphBuilder> graph;
CComPtr<IMediaControl> control;
CComPtr<IMediaEvent>   media_event;
CComPtr<IBaseFilter>   source_filter;
graph.CoCreateInstance(CLSID_FilterGraph);
graph.QueryInterface<IMediaControl>(&control);
graph.QueryInterface<IMediaEvent>(&media_event);
graph->AddSourceFilter(pFilename, pFilename, &source_filter);

DMOを追加する

DMOは直接フィルタグラフへ追加できない。DMOラッパーフィルタを使う。CLSID_DMOWrapperFilterのインスタンスを作成しIDMOWrapperFilterインターフェイスを取得する。IDMOWrapperFilter::Initで、どのDMOを使うのか指定する。最後にIFilterGraph::AddFilterでフィルタを追加する。(IGraphBuilderIFilterGraphを継承している。)

1
2
3
4
5
6
7
CComPtr<IBaseFilter>   wm_audio;
wm_audio.CoCreateInstance(CLSID_DMOWrapperFilter);
CComPtr<IDMOWrapperFilter> dmo_wrapper;
wm_audio.QueryInterface<IDMOWrapperFilter>(&dmo_wrapper);
dmo_wrapper->Init(CLSID_CWMADecMediaObject
    , DMOCATEGORY_AUDIO_DECODER);
graph->AddFilter(wm_audio, L"WMA decoder");

DirectShowフィルタを追加する

DirectShowフィルタを追加するにはフィルタのクラスIDを指定してインスタンスを作成しIFilterGraph::AddFilterでフィルタを追加する。(フィルタを追加する前に、必要に応じて設定を行うことができる。)

1
2
ds_renderer.CoCreateInstance(CLSID_DSoundRender);
graph->AddFilter(ds_renderer,TEXT("DirectSound Renderer"));

フィルタ上のピン同士を直接接続する

フィルタグラフへフィルタを追加したら、フィルタ上のピン同士を接続する。

  1. 接続元フィルタの出力ピン、接続先フィルタの入力ピンを取得する。ピンはIPinインターフェイスで表される。
  2. 出力ピンから入力ピンに向かって接続する。

まず、フィルタに備わっているピンを取得する。IBaseFilter::EnumPinsを使ってフィルタの持つピンの列挙子IEnumPinsを取得する。IEnumPins::Nextでフィルタを1つずつ取得できる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ピンを取得する
HRESULT
GetPin
(IBaseFilter *pFilter, PIN_DIRECTION dir, CComPtr<IPin> &pin, GUID majorType)
{
    HRESULT retCode=E_FAIL;
    HRESULT hr=NOERROR;
    CComPtr<IEnumPins> e;
    hr=pFilter->EnumPins(&e);
    if(FAILED(hr))
        return hr;
    while(e->Next(1, &pin.p, NULL) == S_OK) {

次に、取得したピンが出力ピンなのか入力ピンなのか判定する。IPin::QueryDirectionを使う。

1
2
PIN_DIRECTION this_direction;
        hr=pin->QueryDirection(&this_direction);

this_directionには、入力ピンならPINDIR_INPUT、出力ピンならPINDIR_OUTPUTが代入されている。

WM ASF リーダーフィルタを見ると出力ピンが2つあるはず。取得した出力ピンが映像/音声のどちらを出力するのか判定する必要がある。それにはIPin::EnumMediaTypesを使う。

IPin::EnumMediaTypesで、メディアタイプの列挙子IEnumMediaTypesを取得しIEnumMediaTypes::Nextでメディアタイプを1つずつ取得できる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
CComPtr<IEnumMediaTypes> em;
AM_MEDIA_TYPE *amt;
hr=pin->EnumMediaTypes(&em);
if(SUCCEEDED(hr)) {
    while(em->Next(1,&amt,NULL)==S_OK) {
        GUID mj=amt->majortype;
        // amt を解放
        if (amt->cbFormat!=NULL) {
            CoTaskMemFree((PVOID)amt->pbFormat);
        }
        if(amt->pUnk)
            amt->pUnk->Release();
        CoTaskMemFree(amt);
        if(mj==majorType) {
            retCode=S_OK;
            break;
        }
    }
}

接続する入力ピンと出力ピンを特定できたらIFilterGraph::ConnectDirectを呼び出して接続する。

  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
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include "stdafx.h"
LPCWSTR MovieFile=L"C:\\Users\\Public\\Videos\\Sample Videos\\Bear.wmv";
// ピンを取得する
HRESULT
GetPin
(IBaseFilter *pFilter, PIN_DIRECTION dir, CComPtr<IPin> &pin, GUID majorType)
{
    HRESULT retCode=E_FAIL;
    HRESULT hr=NOERROR;
    CComPtr<IEnumPins> e;
    hr=pFilter->EnumPins(&e);
    if(FAILED(hr))
        return hr;
    while(e->Next(1, &pin.p, NULL) == S_OK) {
        if(!pin) {
            continue;
        }
        PIN_DIRECTION this_direction;
        hr=pin->QueryDirection(&this_direction);
        if(FAILED(hr)) {
            continue;
        }
        if(this_direction!=dir) {
            continue;
        }
        if(majorType == GUID_NULL) {
            retCode=S_OK;
            break;
        }
        CComPtr<IEnumMediaTypes> em;
        AM_MEDIA_TYPE *amt;
        hr=pin->EnumMediaTypes(&em);
        if(SUCCEEDED(hr)) {
            while(em->Next(1,&amt,NULL)==S_OK) {
                GUID mj=amt->majortype;
                // amt を解放
                if (amt->cbFormat!=NULL) {
                    CoTaskMemFree((PVOID)amt->pbFormat);
                }
                if(amt->pUnk)
                    amt->pUnk->Release();
                CoTaskMemFree(amt);
                if(mj==majorType) {
                    retCode=S_OK;
                    return retCode;
                }
            }
        }
    }
    return retCode;
}
// フィルタの同士を接続する
HRESULT
ConnectFilter
(IGraphBuilder *pBuilder, IBaseFilter *pSrc, IBaseFilter *pDest, GUID majorType)
{
    HRESULT hr=NOERROR;
    CComPtr<IPin> in;
    CComPtr<IPin> out;
    hr=GetPin(pSrc, PINDIR_OUTPUT, out, majorType);
    if(FAILED(hr)) {
        return hr;
    }
    hr=GetPin(pDest, PINDIR_INPUT, in, GUID_NULL);
    if(SUCCEEDED(hr)) {
        hr=pBuilder->ConnectDirect(out, in, NULL);
    }
    return hr;
}
void Play()
{
    HRESULT hr;
    CComPtr<IGraphBuilder> graph;
    CComPtr<IMediaControl> control;
    CComPtr<IMediaEvent>   media_event;
    CComPtr<IBaseFilter>   source_filter;
    graph.CoCreateInstance(CLSID_FilterGraph);
    graph.QueryInterface<IMediaControl>(&control);
    graph.QueryInterface<IMediaEvent>(&media_event);
    graph->AddSourceFilter(MovieFile, pFilename, &source_filter);
    CComPtr<IBaseFilter>   wm_audio;
    CComPtr<IBaseFilter>   wm_video;
    CComPtr<IBaseFilter>   video_renderer;
    CComPtr<IBaseFilter>   ds_renderer;
    // WMA
    wm_audio.CoCreateInstance(CLSID_DMOWrapperFilter);
    CComPtr<IDMOWrapperFilter> dmo_wrapper;
    wm_audio.QueryInterface<IDMOWrapperFilter>(&dmo_wrapper);
    dmo_wrapper->Init(CLSID_CWMADecMediaObject
        , DMOCATEGORY_AUDIO_DECODER);
    graph->AddFilter(wm_audio, L"WMA decoder");
    dmo_wrapper.Release();
    // WMV
    wm_video.CoCreateInstance(CLSID_DMOWrapperFilter);
    wm_video.QueryInterface<IDMOWrapperFilter>(&dmo_wrapper);
    dmo_wrapper->Init(CLSID_CWMVDecMediaObject
        , DMOCATEGORY_VIDEO_DECODER);
    graph->AddFilter(wm_video, L"WMV decoder");
    // Video Renderer
    video_renderer.CoCreateInstance(CLSID_VideoRenderer);
    graph->AddFilter(video_renderer,TEXT("Video Renderer"));
    // DirectSound Renderer
    ds_renderer.CoCreateInstance(CLSID_DSoundRender);
    graph->AddFilter(ds_renderer,TEXT("DirectSound Renderer"));
    // connect
    hr=ConnectFilter(graph, source_filter, wm_video,  MEDIATYPE_Video);
    hr=ConnectFilter(graph, wm_video, video_renderer, MEDIATYPE_Video);
    hr=ConnectFilter(graph, source_filter, wm_audio,  MEDIATYPE_Audio);
    hr=ConnectFilter(graph, wm_audio, ds_renderer, MEDIATYPE_Audio);
    hr = control->Run();
    long evCode;
    media_event->WaitForCompletion(INFINITE, &evCode);
}
int _tmain(int argc, _TCHAR* argv[])
{
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    Play();
    CoUninitialize();
    return 0;
}

https://github.com/mahorigahama/DShowPlay2


Comments

comments powered by Disqus