動機
現在、Caputraというデスクトップキャプチャソフトに、AviUtlを使っている人ならおなじみの可逆圧縮codecである、Ut Video Codec Suiteを対応させる試みを行っています。
その過程で、codecの設定をわざわざレジストリをいじるのは面倒だし、設定画面とか呼べないの?と思ったのですね。
例えばAviUtlなら
この設定ボタンから呼べる
こういうやつです。
このウィンドウは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を生成しているではないですか。
case ICM_CONFIGURE:
if (lParam1 == -1)
return pCodec->QueryConfigure();
else
return pCodec->Configure((HWND)lParam1);
LRESULT CVCMCodec::Configure(HWND hwnd)
{
if (m_mode != ICMODE_COMPRESS)
return ICERR_UNSUPPORTED;
return m_pCodec->Configure(hwnd) == 0 ? ICERR_OK : ICERR_UNSUPPORTED;
}
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_mode
はICMODE_COMPRESS
でなければならない -
lParam1 == -1
より、親ウィンドウのハンドルが必須
初期化
しかし、その前にpCodec
とかm_mode
とかいう変数が見えます。これらはどうやって初期化されているのでしょうか?
LRESULT CALLBACK DriverProc(DWORD_PTR dwDriverId, HDRVR hdrvr, UINT uMsg, LPARAM lParam1, LPARAM lParam2)
{
CVCMCodec *pCodec = (CVCMCodec *)dwDriverId;
変数の定義を見るだけでは、プロージャーの引数から渡ってくるんだなとしかわかりません。
case DRV_OPEN:
return (LRESULT)CVCMCodec::Open((ICOPEN *)lParam2);
case DRV_CLOSE:
if (pCodec != NULL)
delete pCodec;
return TRUE;
もう少し下を見るとDRV_CLOSE
を受け取ったときに、pCodec
をdelete
しているところが見つかりました。さらにその上にDRV_OPEN
というそれっぽいのがあります。CVCMCodec::Open
を見てみましょう。
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_OPEN
とDRV_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で作りましょう。
<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>
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
無事に呼び出せました。
余談: 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
にあった。