DirectShowによる仮想カメラ開発のメモ

#1. はじめに
DirectShowによる仮想カメラ開発の機会がありましたので、参考にした記事等を記載いたします。
ここではDirectShowによる具体的な実装の説明は省かせていただきます。

#2. 仮想カメラ開発の先駆者さま方による記事
これをみて勉強しました。
##① 日本語で分かりやすい仮想カメラ実装の解説記事

https://qiita.com/HexagramNM/items/2311f025f3af758c83a0

https://github.com/HexagramNM/NMVCamFilter
##② 海外でポピュラーな仮想カメラ実装の最新コード

https://alax.info/blog/2161

https://github.com/roman380/tmhare.mvps.org-vcam
###Visual Studio 2019 に対応
DirectShowのコア部分たるBaseClassesは開発元のMicrosoftによる更新が滞っており、これまで開発環境を選ぶ状態でした。が！
たまたまBaseClassesをVisual Studio 2019にアップグレードしたものをみつけました。上記の仮想カメラ実装で使われております。

https://alax.info/blog/2157

https://github.com/roman380/Windows-classic-samples/tree/directshow/Samples/Win7Samples/multimedia/directshow
#3. 仮想カメラのコードをみてみよう

手っ取り早く実装したい方は、2項 ② の仮想カメラのコードをベースに、以下の項目を弄るとよいでしょう。
##① 映像を出力するモジュールの初期化処理

```c++:Filters/Filters.cpp
CVCamStream::CVCamStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) :
    CSourceStream(NAME("Virtual Cam"),phr, pParent, pPinName), m_pParent(pParent)
{
    ////////////////////

    // 映像出力モジュールの初期化処理を書く

    ////////////////////
    
    // Set the default media type as 320x240x24@15
    GetMediaType(4, &m_mt);
}
```

##② 映像を出力するモジュールの更新処理

```c++:Filters/Filters.cpp
HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
{
    REFERENCE_TIME rtNow;
    
    REFERENCE_TIME avgFrameTime = ((VIDEOINFOHEADER*)m_mt.pbFormat)->AvgTimePerFrame;

    rtNow = m_rtLastTime;
    m_rtLastTime += avgFrameTime;
    pms->SetTime(&rtNow, &m_rtLastTime);
    pms->SetSyncPoint(TRUE);

    BYTE *pData;
    long lDataLen;
    pms->GetPointer(&pData);

    ////////////////////

    // 出力映像の更新
    // output : RGB24

    // 例. ランダム画像を出力する
    lDataLen = pms->GetSize();
    for(int i = 0; i < lDataLen; ++i)
        pData[i] = rand();
    // 例. おわり

    ////////////////////

    return NOERROR;
} // FillBuffer
```
②仮想カメラの横幅、高さ、FPS

```c++:Filters/Filters.cpp
// See Directshow help topic for IAMStreamConfig for details on this method
HRESULT CVCamStream::GetMediaType(int iPosition, CMediaType *pmt)
{
    if(iPosition < 0) return E_INVALIDARG;
    if(iPosition > 8) return VFW_S_NO_MORE_ITEMS;

    if(iPosition == 0) 
    {
        *pmt = m_mt;
        return S_OK;
    }

    DECLARE_PTR(VIDEOINFOHEADER, pvi, pmt->AllocFormatBuffer(sizeof(VIDEOINFOHEADER)));
    ZeroMemory(pvi, sizeof(VIDEOINFOHEADER));

    pvi->bmiHeader.biCompression = BI_RGB;
    pvi->bmiHeader.biBitCount    = 24;
    pvi->bmiHeader.biSize       = sizeof(BITMAPINFOHEADER);

    ////////////////////
    // 第一引数 iPositionによって横幅、高さを設定する
    pvi->bmiHeader.biWidth      = 80 * iPosition;
    pvi->bmiHeader.biHeight     = 60 * iPosition;
    ////////////////////

    pvi->bmiHeader.biPlanes     = 1;
    pvi->bmiHeader.biSizeImage  = GetBitmapSize(&pvi->bmiHeader);
    pvi->bmiHeader.biClrImportant = 0;

    pvi->AvgTimePerFrame = 1000000;

    SetRectEmpty(&(pvi->rcSource)); // we want the whole image area rendered.
    SetRectEmpty(&(pvi->rcTarget)); // no particular destination rectangle

    pmt->SetType(&MEDIATYPE_Video);
    pmt->SetFormatType(&FORMAT_VideoInfo);
    pmt->SetTemporalCompression(FALSE);

    // Work out the GUID for the subtype from the header info.
    const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
    pmt->SetSubtype(&SubTypeGUID);
    pmt->SetSampleSize(pvi->bmiHeader.biSizeImage);
    
    return NOERROR;

} // GetMediaType
```

（追記予定）

③仮想カメラの名前、識別用ID
（追記予定）



#（余談）配布されている汎用仮想カメラの最強は?
##OBS Studioの仮想カメラでなんでもできます
OBS Studio v26以降にて標準搭載の仮想カメラ機能により、OBS画面をそのままフィルタに流せます。普通の仮想カメラの用途ならこれで十分。

###OBSで仮想カメラ4個ほしい！
OBS Studio 25.08 + obs-virtualcam 2.0.5

https://kii-memo.blogspot.com/2020/06/obs-virtual-cam-v2-0-5.html

obs-virtualcam のソースコードについても仮想カメラ実装例として参考になるかと思います。



#以上
時間ができたら追記いたします。