単純にIBasicVideoEffectを継承したクラスを作成して、MediaPlayer.AddVideoEffect()に渡したいところですが、やんごとなき理由でアプリとは別のプロジェクト(Windowsランタイムコンポーネント)として作成する必要があります。
1:Windowsランタイムコンポーネントとしてプロジェクト追加する
(WinAppSDKではない)UWPの場合は上の .Net ネイティブ の方を選択します
2:win2d.uwp のnuget参照を追加
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.14</Version>
</PackageReference>
<PackageReference Include="Win2D.uwp">
<Version>1.28.3</Version>
</PackageReference>
</ItemGroup>
3:ColorAdjustmentEffectクラスを追加
namespace VideoEffects;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using System.Collections.Generic;
using Windows.Foundation.Collections;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Media.Effects;
using Windows.Media.MediaProperties;
public static class PropertySetExtensionsForColorAdjustmentEffect
{
public static bool IsDefaultBrightness(float v) => v == 0;
public static bool IsDefaultContrast(float v) => v == 1;
public static bool IsDefaultSaturation(float v) => v == 1;
public static void SetBrightness(this IPropertySet ps, float value)
{
ps["Brightness"] = value;
}
public static void SetContrast(this IPropertySet ps, float value)
{
ps["Contrast"] = value;
}
public static void SetSaturation(this IPropertySet ps, float value)
{
ps["Saturation"] = value;
}
}
public sealed class ColorAdjustmentEffect : IBasicVideoEffect
{
private CanvasDevice _canvasDevice;
private ColorMatrixEffect _colorMatrixEffect;
private SaturationEffect _saturationEffect;
private PropertySet _properties;
// パラメーター(デフォルト値)
private float _brightness = 0.0f; // Exposure: -1.0 〜 1.0
private float _contrast = 1.0f; // Contrast: 0.0 〜 2.0
private float _saturation = 1.0f; // Saturation: 0.0(モノクロ)〜 2.0+
// GPU描画処理の有効化
public bool UseGraphicsDeviceGpu => true;
public MediaMemoryTypes SupportedMemoryTypes => MediaMemoryTypes.Gpu;
public bool IsReadOnly => false;
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties => new List<VideoEncodingProperties>();
public bool TimeIndependent => true;
public void SetProperties(IPropertySet configuration)
{
if (_properties != null)
{
_properties.MapChanged -= _properties_MapChanged;
}
_properties = (PropertySet)configuration;
_properties.MapChanged += _properties_MapChanged;
UpdateParameters();
}
private void _properties_MapChanged(IObservableMap<string, object> sender, IMapChangedEventArgs<string> @event)
{
if (@event.Key.Equals("Brightness", System.StringComparison.Ordinal)
&& sender.TryGetValue("Brightness", out object b)
&& b is float brightness)
{
_brightness = brightness;
}
else if (@event.Key.Equals("Contrast", System.StringComparison.Ordinal)
&& sender.TryGetValue("Contrast", out object c)
&& c is float contrast)
{
_contrast = contrast;
}
else if (@event.Key.Equals("Saturation", System.StringComparison.Ordinal)
&& sender.TryGetValue("Saturation", out object s)
&& s is float saturation)
{
_saturation = saturation;
}
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
// Media Foundation から渡された Direct3D デバイスから CanvasDevice を作成
_canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
_colorMatrixEffect = new ColorMatrixEffect();
_saturationEffect = new SaturationEffect();
}
public void ProcessFrame(ProcessVideoFrameContext context)
{
// 入力フレームと出力フレームの D3D サーフェスから CanvasBitmap / CanvasRenderTarget を作成
using (var inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(_canvasDevice, context.InputFrame.Direct3DSurface))
using (var renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(_canvasDevice, context.OutputFrame.Direct3DSurface))
using (var ds = renderTarget.CreateDrawingSession())
{
ICanvasImage targetSource = inputBitmap;
if (_contrast != 1 && _brightness != 0)
{
float offset = (1.0f - _contrast) * 0.5f + _brightness;
_colorMatrixEffect.Source = targetSource;
_colorMatrixEffect.ColorMatrix = new Matrix5x4
{
M11 = _contrast, M12 = 0, M13 = 0, M14 = 0,
M21 = 0, M22 = _contrast, M23 = 0, M24 = 0,
M31 = 0, M32 = 0, M33 = _contrast, M34 = 0,
M41 = 0, M42 = 0, M43 = 0, M44 = 1,
M51 = offset, M52 = offset, M53 = offset, M54 = 0
};
targetSource = _colorMatrixEffect;
}
// 3. 彩度 (SaturationEffect)
if (_saturation != 1)
{
_saturationEffect.Source = targetSource;
_saturationEffect.Saturation = _saturation;
targetSource = _saturationEffect;
}
// 最終結果を出力サーフェスに描画
ds.DrawImage(targetSource);
}
}
private void UpdateParameters()
{
if (_properties == null) return;
if (_properties.TryGetValue("Brightness", out object b) && b is float fb)
_brightness = fb;
if (_properties.TryGetValue("Contrast", out object c) && c is float fc)
_contrast = fc;
if (_properties.TryGetValue("Saturation", out object s) && s is float fs)
_saturation = fs;
}
public void Close(MediaEffectClosedReason reason)
{
_canvasDevice?.Dispose();
_colorMatrixEffect?.Dispose();
if (_properties != null)
{
_properties.MapChanged -= _properties_MapChanged;
}
}
public void DiscardQueuedFrames() { }
}
4:プロジェクト参照を追加
VisualStudioのソリューションエクスプローラのプロジェクトのメニューから追加を選択し、追加したプロジェクトを参照するように設定します。
5:MediaPlayer.AddVideoEffectを呼ぶ
IPropertySet _myVideoEffectConfig = new PropertySet();
_mediaPlayer.AddVideoEffect(typeof(ColorAdjustmentEffect).FullName, false, _myVideoEffectConfig);
// 例:明るさの値を変更する
// 前述のPropertySetExtensionsForColorAdjustmentEffectを参照のこと
_myVideoEffectConfig.SetBrightness(1.2f);
PropertySetはIObservableMapを実装しているためProperties["Brightness"] = 0f;のようにキーに対して値を設定すると映像効果として反映されます。
以上です。
参考
自作の動画プレイヤーアプリの実装が確認できます
動画エフェクトのプロジェクト
https://github.com/tor4kichi/TsubameViewer/tree/develop/VideoEffects
MediaPlayer.AddVideoEffectを呼び出している箇所
https://github.com/tor4kichi/TsubameViewer/blob/eebd8a65d7410f046e319bf01a8382d885667e39/TsubameViewer/Views/MovieViewerPage.xaml.cs#L1078

