LoginSignup
2
1

More than 3 years have passed since last update.

Vfwなcodecの設定画面を呼びたい

Posted at

動機

現在、Caputraというデスクトップキャプチャソフトに、AviUtlを使っている人ならおなじみの可逆圧縮codecである、Ut Video Codec Suiteを対応させる試みを行っています

その過程で、codecの設定をわざわざレジストリをいじるのは面倒だし、設定画面とか呼べないの?と思ったのですね。

例えばAviUtlなら
image.png
この設定ボタンから呼べる
image.png
こういうやつです。

このウィンドウはAviUtlに固有のものではなくて他のソフトからも同じものが出るということは、Ut Video Codec Suite側で生成しているものだと推測できます。

Ut Video Codec Suiteの実装を読む

幸いなことにソースコードはGitHubで公開されているので読んでいきます。

https://github.com/umezawatakeshi/utvideo

するとそういえばVFW pluginだったなと思い出してきました。

VfwというのはVideo for Windowsの略で、Win32API触ったことある人ならわかるであろう、プロージャーを書いてメッセージを受け取っていくタイプの書き方で、動画のエンコードやデコードなどなどを実装したdllを登録することで、いろいろなソフトウェアからそれが呼べる仕組みのことです。Windows Media Playerなんかはこれをつかって再生していたと思います。Windows 10標準搭載の動画アプリはMicrosoft Media Foundationという別のものを使っていますが。

なんか検索してると某有名ゲームエンジンの関連のVexe Frameworkのほうが引っかかってくるようですが・・・。

設定画面の呼び出し

いろいろ読んでいくと、次のような箇所を見つけました。つまりICM_CONFIGUREを受け取ったプロージャーは、DialogBoxParamによってWindowを生成しているではないですか。

utv_vcm/DriverProc.cpp
    case ICM_CONFIGURE:
        if (lParam1 == -1)
            return pCodec->QueryConfigure();
        else
            return pCodec->Configure((HWND)lParam1);

utv_vcm/VCMCodec.cpp
LRESULT CVCMCodec::Configure(HWND hwnd)
{
    if (m_mode != ICMODE_COMPRESS)
        return ICERR_UNSUPPORTED;

    return m_pCodec->Configure(hwnd) == 0 ? ICERR_OK : ICERR_UNSUPPORTED;
}

utv_core/UL00Codec.cpp
INT_PTR CUL00Codec::Configure(HWND hwnd)
{
    DialogBoxParam(hModule, MAKEINTRESOURCE(IDD_UL00_CONFIG), hwnd, DialogProc, (LPARAM)this);
    return 0;
}

つまりVfw codecに対してICM_CONFIGUREメッセージを投げられれば良さそうです。

ただし

  • m_mode != ICMODE_COMPRESSより、m_modeICMODE_COMPRESSでなければならない
  • lParam1 == -1より、親ウィンドウのハンドルが必須

初期化

しかし、その前にpCodecとかm_modeとかいう変数が見えます。これらはどうやって初期化されているのでしょうか?

LRESULT CALLBACK DriverProc(DWORD_PTR dwDriverId, HDRVR hdrvr, UINT uMsg, LPARAM lParam1, LPARAM lParam2)
{
    CVCMCodec *pCodec = (CVCMCodec *)dwDriverId;

変数の定義を見るだけでは、プロージャーの引数から渡ってくるんだなとしかわかりません。

utv_vcm/DriverProc.cpp
    case DRV_OPEN:
        return (LRESULT)CVCMCodec::Open((ICOPEN *)lParam2);

    case DRV_CLOSE:
        if (pCodec != NULL)
            delete pCodec;
        return TRUE;

もう少し下を見るとDRV_CLOSEを受け取ったときに、pCodecdeleteしているところが見つかりました。さらにその上にDRV_OPENというそれっぽいのがあります。CVCMCodec::Openを見てみましょう。

utv_vcm/VCMCodec.cpp
CVCMCodec *CVCMCodec::Open(ICOPEN *icopen)
{
    if (IsLogWriterInitializedOrDebugBuild())
    {
        if (icopen != NULL)
            LOGPRINTF("CVCMCodec::Open(icopen=%p, icopen->fccType=%08X, icopen->fccHandler=%08X icopen->dwFlags=%08X)", icopen, icopen->fccType, icopen->fccHandler, icopen->dwFlags);
        else
            LOGPRINTF("CVCMCodec::Open(icopen=NULL)");
    }

    union
    {
        DWORD fccHandler;
        char fccChar[4];
    };

    DWORD icmode;

    if (icopen != NULL)
    {
        if (icopen->fccType != ICTYPE_VIDEO)
            return NULL;
        fccHandler = icopen->fccHandler;
        // なぜか小文字で渡されることがあるので、最初に大文字化しておく。
        for (int i = 0; i < 4; i++)
            fccChar[i] = toupper(fccChar[i]);

        icopen->dwError = ICERR_UNSUPPORTED;
        switch (icopen->dwFlags)
        {
        case ICMODE_COMPRESS:
            if (CheckInterfaceDisabledAndLog("VCM", "Encoder"))
                return NULL;
            break;
        case ICMODE_DECOMPRESS:
            if (CheckInterfaceDisabledAndLog("VCM", "Decoder"))
                return NULL;
            break;
        case ICMODE_QUERY:
            if (CheckInterfaceDisabledAndLog("VCM", "Query"))
                return NULL;
            break;
        default:
            return NULL;
        }

        icopen->dwError = ICERR_OK;
        icmode = icopen->dwFlags;
    }
    else
    {
        fccHandler = (DWORD)-1;
        icmode = 0;
    }

    return new CVCMCodec(fccHandler, icmode);
}

細かいところは読み飛ばすとして、まずicmode = icopen->dwFlags;が目に入ります。さっき言ってた変数ですね!さらにこの関数はreturn new CVCMCodec(fccHandler, icmode);してます。確かにDRV_OPENDRV_CLOSEは対応関係にあることがわかります。

どうやってVfwなcodecにメッセージを投げるか

つまり、どうにかしてコーデックのプロージャーにメッセージを投げられればいいわけです。

ただ、検索の仕方が悪くてなかなかたどり着けず、

のソースコードを読み漁ってました。

結局使う関数は

の3つだとわかりました。

ICOpenは次のようなプロトタイプ宣言で定義されています。

HIC VFWAPI ICOpen(
  DWORD fccType,
  DWORD fccHandler,
  UINT  wMode
);

fccというのが見えますが、これはFOURCCのことです。

FourCC - Wikipedia
FourCC (フォーシーシー) とは、データフォーマットを一意に識別するための4バイトの並びである(four-character code の意)。

fccTypeにはVIDCという文字列をFourCCとして整数に変換したもの、wModeには先に述べたようにICMODE_COMPRESSを渡すとして、fccHandlerには何を渡せばいいのでしょうか?今回はUt Video Codec Suiteの設定画面を呼びたいので、早速FourCCを調べましょう。

といっても公式で
Ut Video Codec Suite 21.2.0 readme (日本語)FourCC 一覧
にまとめられているのでそれを見るだけですね。

とりあえず今回はULH2にしましょう。

実装

arikitari na world!

親ウィンドウがないといけないので、雑にWPFで作りましょう。

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="VfwConfigCall" Height="100" Width="300">
    <Grid>
        <Button Margin="10" Click="SettingClick">Setting</Button>
    </Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void SettingClick(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("arikitari na world!");
        }
    }
}

まずはこれで全世界の人がまずは表示させるであろう恒例のarikitari na world!を表示させて動いたことを確認したら本実装に入りましょう。

定数

vfw.hをみつつ必要なものを取ってきましょう。

        private const int DRV_USER = 0x4000;
        private const int ICM_RESERVED_LOW = DRV_USER + 0x1000;
        private const int ICM_RESERVED = ICM_RESERVED_LOW;
        private const int ICM_CONFIGURE = ICM_RESERVED + 10;
        private const int ICMODE_COMPRESS = 1;

DLL読み込み

DLLを読まないと関数が呼べないので読み込んでおきましょう。

        private const string VFW_DLL = "msvfw32.dll";
        [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
        public static extern IntPtr ICOpen(uint fccType, uint fccHandler, int mode);
        [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
        public static extern int ICClose(IntPtr handle);
        [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
        public static extern int ICSendMessage(IntPtr handle, int message, IntPtr param1, IntPtr param2);

設定画面の呼び出し

あとはclickイベントハンドラを書き換えて設定画面を呼びましょう。FourCCの計算が面倒だったのでSharpAviをNuGetでプロジェクトに追加しておきます。

using System;
using System.Runtime.InteropServices;
using System.Windows;
using SharpAvi;

        private void SettingClick(object sender, RoutedEventArgs e)
        {
            var compressorHandle = ICOpen((uint)KnownFourCCs.CodecTypes.Video, (uint)new FourCC("ULH2"), ICMODE_COMPRESS);
            if (compressorHandle == IntPtr.Zero)
            {
                return;
            }
            try
            {
                var re = ICSendMessage(compressorHandle, ICM_CONFIGURE, new System.Windows.Interop.WindowInteropHelper(this).Handle, IntPtr.Zero);
            }
            finally
            {
                ICClose(compressorHandle);
            }
        }

結果

実行する前に忘れずにUt Video Codec Suiteをインストールしておきましょう。

或るプログラマの一生 » Ut Video Codec Suite

image.png

無事に呼び出せました。

余談: FourCCとcodecの関連付けの管理

FourCCとcodecの関連付けはレジストリで行われている。例えばUt Video Codec Suiteの場合、次のような項目が登録される。

REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Drivers32]
"VIDC.ULRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQY0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMY4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMH2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMH4"="C:\\Windows\\system32\\utv_vcm.dll"

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Drivers32]
"VIDC.ULRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQY0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMY4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMH2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMH4"="C:\\Windows\\system32\\utv_vcm.dll"

ではコーデックのあるFourCCを列挙するにはどうすればいいのか。レジストリを自分で読みたくはない。

どうやらICInfoという関数を使えばいいようだ。使い方のサンプルが
Locating and Opening Compressors and Decompressors - Win32 apps | Microsoft Docs
にあった。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1