Microsoft DirectX 8.0

AVI ファイルの再圧縮

このトピックでは、キャプチャ グラフ ビルダを使って AVI (Audio-Video Interleaved) ファイルを再圧縮する方法を解説する。キャプチャ グラフ ビルダは、キャプチャ グラフやその他のカスタム フィルタ グラフを構築する COM オブジェクトである。

注 :  Microsoft® DirectShow® に付属するキャプチャ グラフ ビルダには 2 種類のバージョンがある。それぞれ異なるクラス識別子を持ち、異なるインターフェイスを公開する。古い方のバージョンは、CLSID_CaptureGraphBuilder のクラス識別子を持ち、ICaptureGraphBuilder インターフェイスを公開する。これは、既存のアプリケーションとの互換性のためにサポートされている。新しいバージョンは、CLSID_CaptureGraphBuilder2 のクラス識別子を持ち、柔軟性が増した新しい ICaptureGraphBuilder2 インターフェイスを公開する。

ビデオ キャプチャ アプリケーションでの ICaptureGraphBuilder2 の使用については、「キャプチャ アプリケーションの書き方」を参照すること。ここでは、ICaptureGraphBuilder2 を使って、メディア データをソース ファイルから出力ファイルにコピーし、その途中でデータを再圧縮する。

このトピックは、以下のセクションで構成される :

グラフの作成

まず、以下の例に示すように、キャプチャ グラフ ビルダのインスタンスを生成する。

ICaptureGraphBuilder2   *pBuild = NULL;
CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, 
        IID_ICaptureGraphBuilder2, (void **)&pBuild);

キャプチャ グラフ ビルダは、自動的にフィルタ グラフ マネージャを作成する。最初はフィルタ グラフは空で、フィルタを持っていない。フィルタ グラフは、以下の 2 段階の処理で構築する。

  1. グラフのレンダリング セクションを構築する。このセクションには、AVI MUX フィルタとファイル ライタが含まれる。MUX フィルタは、ビデオ ストリームとオーディオ ストリームを、ファイル ライタ用の 1 つの AVI ストリームに結合する。
  2. ファイル ソース フィルタをグラフに追加し、それを MUX フィルタに接続する。キャプチャ グラフ ビルダは、ファイル フォーマットを解析するために必要なスプリッタ フィルタとデコーダ フィルタを挿入する。必要な場合は、オーディオおよびビデオ ストリームを、オーディオおよびビデオ圧縮フィルタに通すこともできる。

レンダリング セクションの構築

グラフのレンダリング セクションを構築するには、ICaptureGraphBuilder2::SetOutputFileName メソッドを呼び出す。このメソッドは、出力のメディア サブタイプと出力ファイルの名前を指定する入力パラメータを受け取り、MUX フィルタとファイル ライタへのポインタが格納された出力パラメータを返す。MUX フィルタへのポインタは、グラフ構築の次の段階で必要になる。ファイル ライタへのポインタはこの例では必要ないので、NULL にすることができる。コードを短く保つために、以下の例ではファイル名にリテラル文字列を使用している。

IBaseFilter *pMux = NULL;
pBuild->SetOutputFileName(
        &MEDIASUBTYPE_Avi,  // ファイル タイプ。
        L"Output.avi",// ファイル名。
        &pMux,              // マルチプレクサへのポインタを受け取る。
        NULL);                  // ファイル ライタへのポインタを受け取る。

このメソッドが返ると、MUX フィルタへのポインタの参照カウントが大きくなっているので、アプリケーションは終了時にポインタを解放しなければならない。MUX フィルタは、AVI フォーマットを制御するために以下のインターフェイスを公開している。

次の図は、現状のフィルタ グラフを示す。

フィルタ グラフのレンダリング セクション

ソース フィルタの接続

次の作業は、グラフの残りの部分へのソース フィルタの接続である。まず、ICaptureGraphBuilder2::GetFiltergraph メソッドを呼び出して、フィルタ グラフの IGraphBuilder インターフェイスへのポインタを取得する。次に、IGraphBuilder::AddSourceFilter メソッドを呼び出す。このメソッドは、ファイルをロードし、ファイル ソース フィルタへのポインタを取得する。

IGraphBuilder   *pGraph = NULL;
IBaseFilter     *psrc = null;

pBuild->GetFiltergraph(&pGraph);
pGraph->AddSourceFilter(L"C:\\Input.avi", L"Source Filter", &pSrc);

次に、ファイルの再圧縮に使用するビデオおよびオーディオ圧縮フィルタのインスタンスを生成する。1 つの方法として、ユーザーのシステムにある圧縮デバイスを列挙して、ユーザーがデバイスを選択するようにできる。この方法については、「デバイスとフィルタの列挙」を参照すること。別の方法として、使いたい圧縮フィルタがある場合は、CoCreateInstance を呼び出してそのインスタンスを直接生成することもできる。圧縮フィルタのインスタンスを生成したら、IFilterGraph::AddFilter を呼び出してインスタンスをフィルタ グラフに追加する。

以下の例では、ビデオを圧縮する AVI Compressor フィルタのインスタンスを生成している。オーディオは圧縮しないままにしてある。

CoCreateInstance(CLSID_AVICo, NULL, CLSCTX_INPROC,  
        IID_IBaseFilter, (void **)&pVComp); 
pGraph->AddFilter(pVComp, L"Compressor");

次の図に示すように、まだソース フィルタと圧縮フィルタは、グラフ内のほかのものとは接続されていない。

ソース フィルタと圧縮フィルタを伴うフィルタ グラフ

グラフ構築の最後のステップは、ICaptureGraphBuilder2::RenderStream メソッドを使ったビデオおよびオーディオ ストリームのレンダリングである。このメソッドは、ソース フィルタの出力ピンをグラフのレンダリング セクションに接続し、オプションが指定されれば圧縮フィルタを通す。

ICaptureGraphBuilder2 のメソッドは、プレビューおよびキャプチャ用に別々の出力ピンを持てるビデオ カメラなど、生のキャプチャ ソースを扱えるようにデザインされている。そういう状況では、RenderStream の最初の 2 つのパラメータで、ピン カテゴリとメディア タイプを指定することで、どのピンを接続するかを指定する。ファイル ソース フィルタを使用する場合、出力ピンは 1 つだけなので、これらのパラメータを指定する必要はない。

その次の 3 つのパラメータは、ソース フィルタ (または、ソース フィルタのピン)、圧縮フィルタ、およびシンク フィルタへのポインタである。ファイルに書き込む場合は、これらの最後のパラメータはマルチプレクサへのポインタでなければならない。

以下のコードは、ビデオ コンプレッサを介してビデオ ストリームをレンダリングする。

pBuild->RenderStream(
        NULL,       // 出力ピンのカテゴリ
        NULL,       // メディア タイプ
        pSrc,       // ソース フィルタ
        pVComp,     // コンプレッサ フィルタ
        pMux);      // シンク フィルタ (AVI Mux)

次の図は、現状のフィルタ グラフを示す。

レンダリングされたビデオ ストリーム

この図では、ソース ファイルに 2 つのストリーム、すなわちオーディオ ストリームとビデオ ストリームがあると仮定している。その場合、AVI スプリッタ フィルタにはオーディオ ストリーム用の出力ピンが含まれるが、ピンはグラフ内の何にも接続されていない。オーディオ ストリームをレンダリングするには、RenderStream をもう一度呼び出す。

pBuild->RenderStream(NULL, NULL, pSrc, NULL, pMux);

ソース フィルタの唯一の出力ピンは既に接続されているので、RenderStream メソッドはスプリッタ フィルタの未接続の出力ピンを探す。次に、オーディオ出力ピンを見つけ、それを MUX フィルタに直接接続する。ソース ファイルにビデオ ストリームだけがあって、オーディオ ストリームがない場合、2 回目の RenderStream の呼び出しは予想どおり失敗する。しかし、RenderStream への最初の呼び出しでグラフは完成するので、2 回目の呼び出しが失敗しても問題はない。グラフは実行される。

次の図は、完成したフィルタ グラフを示す。

レンダリングされたオーディオおよびビデオ ストリーム

この例では、2 回の呼び出しの順序が重要である。ビデオ ストリームは再圧縮されるが、オーディオ ストリームは再圧縮されないためである。最初の呼び出しにはビデオ コンプレッサが含まれているので、ビデオ ストリームが指定される。2 回目の呼び出しにはコンプレッサが含まれないので、どのストリーム タイプにも適用できる。残っているのはオーディオ ストリームだけなので、この呼び出しは正しく機能する。

ただし、以下に示すように、この順序が入れ替わったとする。

pBuild->RenderStream(NULL, NULL, pSrc, NULL, pMux);
// 間違い。これによってビデオ ストリームがレンダリングされることもある。

pBuild->RenderStream(NULL, NULL, pSrc, pVComp, pMux);
// すると、これは失敗する。

この例では、2 回目の呼び出しでコンプレッサが指定されている。最初の呼び出しによって、ビデオ ストリームとオーディオ ストリームのどちらがレンダリングされるかは未確定であるが、ビデオ ストリームがレンダリングされれば、2 回目の呼び出しは失敗する。

一方、この例でビデオ コンプレッサとオーディオ コンプレッサの両方が使用された場合、順序は問題ではなくなる。それぞれの呼び出しによって、どちらかのストリームが完全に指定されるためである。同じような理由で、圧縮フィルタを使用しない場合も、順序は問題ではなくなる。その場合、2 つの呼び出しは同じになる。

ソース ファイルに 3 つ以上のストリームがある場合、フィルタ グラフは正しく構築されず、予期しない動作を示す。こういう状況はまれであるが、安全のため、最初のストリームをレンダリングした後、スプリッタ フィルタの出力ピンを列挙する必要がある。詳細については、「フィルタ グラフ内のオブジェクトの列挙」を参照すること。

ファイルの書き込み

ファイルを書き込む前に、圧縮フィルタのプロパティを設定できる。サンプル コードには、ビット レートとキー フレーム レートを設定する方法を示している。詳細については、後のサンプル コードを参照すること。

ファイルを書き込むには、フィルタ グラフを実行し、IMediaControl::Run メソッドを呼び出し、再生が終了するのを待ってから、IMediaControl::Stop を呼び出してグラフを明示的に停止しなければならない。停止しないと、ファイルは正しく書き込まれない。詳細については、「イベントへの応答」を参照すること。

ファイル書き込み処理の進行状況を表示するには、IMediaSeeking インターフェイスに関して MUX フィルタに問い合せる。IMediaSeeking::GetDuration メソッドを呼び出し、ファイルの時間幅を取得する。グラフの実行中は、定期的に IMediaSeeking::GetCurrentPosition メソッドを呼び出し、ストリームでのグラフの現在位置を取得する。現在位置を時間幅で割ると、完了した割合が得られる。

注 :  通常、アプリケーションは IMediaSeeking に関してはフィルタ グラフ マネージャに問い合わせるべきであり、個別のフィルタにも問い合わせるべきでない。ファイルの書き込みは、この規則の例外である。フィルタ グラフ マネージャは、フィルタから現在位置を取得しない。その代わり、開始位置と、グラフの実行時間を基にして位置を計算する。ファイルの書き込みでは、これによって間違った結果が生じることもある。これは、ファイルの書き込み時間がファイルの時間幅よりも長くなることがあるためである。MUX フィルタから位置を取得すると、正確な結果が得られる。

サンプル コード

より簡潔にするため、以下のコードではエラー チェックは行われない。

#include <dshow.h>
#include <stdio.h>

void __cdecl main(void)
{
    ICaptureGraphBuilder2   *pBuild = NULL;
    IGraphBuilder           *pGraph = NULL;
    IBaseFilter             *psrc = null;       // ソース フィルタ
    IBaseFilter             *pMux = NULL;       // MUX フィルタ
    IBaseFilter             *pVComp = NULL;     // ビデオ コンプレッサ フィルタ

    CoInitialize(NULL);

    // キャプチャ グラフ ビルダを作成する。
    CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, 
            IID_ICaptureGraphBuilder2, (void **)&pBuild);


    // グラフのレンダリング セクションを作成する。
    pBuild->SetOutputFileName(
            &MEDIASUBTYPE_Avi,  // ファイル タイプ。
            L"C:\\Output.avi",          // ファイル名。
            &pMux,              // マルチプレクサへのポインタを受け取る。
            NULL);                  // ファイル ライタへのポインタを受け取る。
    
    // ソース ファイルをロードする。
    pBuild->GetFiltergraph(&pGraph);
    pGraph->AddSourceFilter(L"C:\\Input.avi", L"Source Filter", &pSrc);

    // コンプレッサ フィルタを追加する。
    CoCreateInstance(CLSID_AVICo, NULL, CLSCTX_INPROC,  
            IID_IBaseFilter, (void **)&pVComp); 
    pGraph->AddFilter(pVComp, L"Compressor");

    // コンプレッサを介してビデオ ストリームをレンダリングする。
    pBuild->RenderStream(
            NULL,       // 出力ピンのカテゴリ
            NULL,       // メディア タイプ
            pSrc,       // ソース フィルタ
            pVComp,     // コンプレッサ フィルタ
            pMux);      // シンク フィルタ (AVI Mux)

    // オーディオ ストリームをレンダリングする。
    pBuild->RenderStream(NULL, NULL, pSrc, NULL, pMux);

    // ビデオ圧縮プロパティを設定する。
    IAMStreamConfig         *pStreamConfig = NULL;
    IAMVideoCompression     *pCompress = NULL;

    // 100k/秒のデータ レートで圧縮する。
    AM_MEDIA_TYPE *pmt;
    pBuild->FindInterface(NULL, NULL, pVComp, IID_IAMStreamConfig, (void **)&pStreamConfig);                           
    pStreamConfig->GetFormat(&pmt);
    if (pmt->formattype == FORMAT_VideoInfo) 
    {
        ((VIDEOINFOHEADER *)(pmt->pbFormat))->dwBitRate = 100000;
        pStreamConfig->SetFormat(pmt);
    }
    DeleteMediaType(pmt);

    // 4 つのフレームごとにキー フレームを要求する。
    pStreamConfig->QueryInterface(IID_IAMVideoCompression, (void **)&pCompress);
    pCompress->put_KeyFrameRate(4);
    pCompress->Release();
    pStreamConfig->Release();

// グラフを実行する。
    IMediaControl           *pControl = NULL;
    IMediaSeeking   *pSeek = NULL;
    IMediaEvent             *pEvent = NULL;
    pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
    pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
   
    HRESULT hr = pMux->QueryInterface(IID_IMediaSeeking, (void**)&pSeek);

    pControl->Run();
    printf("Recompressing... \n");
    long evCode;

    if (SUCCEEDED(hr))  // IMediaSeeking がサポートされる
    {
        REFERENCE_TIME rtTotal, rtNow = 0;
        pSeek->GetDuration(&rtTotal);
        while ((pEvent->WaitForCompletion(1000, &evCode)) == E_ABORT)
        {
            pSeek->GetCurrentPosition(&rtNow);
            printf("%d%%\n", (rtNow * 100)/rtTotal);
        }
        pSeek->Release();
    }
    else  // 進行状況を更新できない。
    {

    pEvent->WaitForCompletion(INFINITE, &evCode);
    pControl->Stop();
    printf("All done\n");   

    pSrc->Release();
    pMux->Release();
    pVComp->Release();
    pControl->Release();
    pEvent->Release();
    pBuild->Release();
    pGraph->Release();
    CoUninitialize();
}