Benutzerdefinierte Videoeffekte
In diesem Artikel wird beschrieben, wie Sie eine Windows-Runtime Komponente erstellen, die die IBasicVideoEffect-Schnittstelle implementiert, um benutzerdefinierte Effekte für Videostreams zu erstellen. Benutzerdefinierte Effekte können mit verschiedenen Windows-Runtime-APIs verwendet werden, einschließlich MediaCapture, die Zugriff auf die Kamera eines Geräts und MediaComposition ermöglicht, sodass Sie komplexe Kompositionen aus Medienclips erstellen können.
Hinzufügen eines benutzerdefinierten Effekts zu Ihrer Anwendung
Ein benutzerdefinierter Videoeffekt wird in einer Klasse definiert, die die IBasicVideoEffect-Schnittstelle implementiert. Diese Klasse kann nicht direkt in das Projekt Ihrer Anwendung eingebunden werden. Stattdessen müssen Sie eine Windows-Runtime Komponente verwenden, um Ihre Videoeffektklasse zu hosten.
Hinzufügen einer Windows-Runtime Komponente für ihren Videoeffekt
- Gehen Sie in Microsoft Visual Studio bei geöffneter Lösung in das Menü Datei und wählen Sie Hinzufügen->Neues Projekt.
- Wählen Sie den Projekttyp Windows Runtime Component (Universal Windows).
- Nennen Sie in diesem Beispiel das Projekt "VideoEffectComponent". Auf diesen Namen wird später im Code verwiesen.
- Klicken Sie auf OK.
- Die Projektvorlage erstellt eine Klasse namens Class1.cs. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Symbol für Class1.cs und wählen Sie Umbenennen.
- Benennen Sie die Datei in ExampleVideoEffect.cs um. Visual Studio zeigt eine Eingabeaufforderung an, in der Sie gefragt werden, ob Sie alle Verweise auf den neuen Namen aktualisieren möchten. Klicken Sie auf Ja.
- Öffnen Sie ExampleVideoEffect.cs, und aktualisieren Sie die Klassendefinition, um die IBasicVideoEffect-Schnittstelle zu implementieren.
public sealed class ExampleVideoEffect : IBasicVideoEffect
Sie müssen die folgenden Namespaces in Ihre Effektklassendatei aufnehmen, um auf alle in den Beispielen dieses Artikels verwendeten Typen zugreifen zu können.
using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Windows.Foundation.Collections;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Graphics.Imaging;
Implementieren der IBasicVideoEffect-Schnittstelle mithilfe der Softwareverarbeitung
Ihr Videoeffekt muss alle Methoden und Eigenschaften der IBasicVideoEffect-Schnittstelle implementieren. Dieser Abschnitt führt Sie durch eine einfache Implementierung dieser Schnittstelle, die die Softwareverarbeitung verwendet.
Close-Methode
Das System ruft die Close-Methode für Ihre Klasse auf, wenn der Effekt heruntergefahren werden soll. Sie sollten diese Methode verwenden, um alle Ressourcen zu entsorgen, die Sie erstellt haben. Das Argument der Methode ist ein MediaEffectClosedReason, das Ihnen mitteilt, ob der Effekt normal geschlossen wurde, ob ein Fehler aufgetreten ist oder ob der Effekt das erforderliche Codierungsformat nicht unterstützt.
public void Close(MediaEffectClosedReason reason)
{
// Dispose of effect resources
}
DiscardQueuedFrames-Methode
Die Methode DiscardQueuedFrames wird aufgerufen, wenn Ihr Effekt zurückgesetzt werden soll. Ein typisches Szenario hierfür ist, dass Ihr Effekt zuvor verarbeitete Frames speichert, um sie für die Verarbeitung des aktuellen Frames zu verwenden. Wenn diese Methode aufgerufen wird, sollten Sie den Satz der zuvor gespeicherten Bilder entsorgen. Diese Methode kann verwendet werden, um jeden Zustand zurückzusetzen, der sich auf frühere Frames bezieht, nicht nur akkumulierte Videoframes.
private int frameCount;
public void DiscardQueuedFrames()
{
frameCount = 0;
}
IsReadOnly-Eigenschaft
Die IsReadOnly-Eigenschaft teilt dem System mit, ob der Effekt in die Ausgabe des Effekts geschrieben wird. Wenn Ihre App die Videoframes nicht ändert (z. B. einen Effekt, der nur die Analyse der Videoframes durchführt), sollten Sie diese Eigenschaft auf "true" festlegen, wodurch das System die Frameeingabe effizient in die Frameausgabe kopiert.
Tipp
Wenn die IsReadOnly-Eigenschaft auf "true" festgelegt ist, kopiert das System den Eingabeframe in den Ausgabeframe, bevor ProcessFrame aufgerufen wird. Das Festlegen der IsReadOnly-Eigenschaft auf "true" schränkt das Schreiben in die Ausgabeframes des Effekts in ProcessFrame nicht ein.
public bool IsReadOnly { get { return false; } }
SetEncodingProperties-Methode
Das System ruft SetEncodingProperties auf Ihren Effekt auf, um Sie über die Codierungseigenschaften für den Videostream zu informieren, auf dem der Effekt ausgeführt wird. Diese Methode stellt auch einen Verweis auf das Direct3D-Gerät bereit, das für das Hardwarerendering verwendet wird. Die Verwendung dieses Geräts wird im Hardwareverarbeitungsbeispiel weiter unten in diesem Artikel gezeigt.
private VideoEncodingProperties encodingProperties;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
this.encodingProperties = encodingProperties;
}
SupportedEncodingProperties-Eigenschaft
Das System überprüft die Eigenschaft SupportedEncodingProperties, um festzustellen, welche Kodierungseigenschaften von Ihrem Effekt unterstützt werden. Beachten Sie, dass wenn der Verbraucher Ihres Effekts Videos nicht mit den von Ihnen angegebenen Eigenschaften codieren kann, wird "Close" für Ihren Effekt aufgerufen und der Effekt aus der Videopipeline entfernt.
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var encodingProperties = new VideoEncodingProperties();
encodingProperties.Subtype = "ARGB32";
return new List<VideoEncodingProperties>() { encodingProperties };
// If the list is empty, the encoding type will be ARGB32.
// return new List<VideoEncodingProperties>();
}
}
Hinweis
Wenn Sie eine leere Liste von VideoEncodingProperties-Objekten aus SupportedEncodingProperties zurückgeben, verwendet das System standardmäßig die ARGB32-Codierung.
SupportedMemoryTypes-Eigenschaft
Das System überprüft die SupportedMemoryTypes-Eigenschaft, um festzustellen, ob Ihr Effekt auf Videoframes im Softwarespeicher oder im Hardwarespeicher (GPU) zugreifen soll. Wenn Sie "MediaMemoryTypes.Cpu" zurückgeben, wird ihr Effekt an Eingabe- und Ausgabeframes übergeben, die Bilddaten in SoftwareBitmap-Objekten enthalten. Wenn Sie "MediaMemoryTypes.Gpu" zurückgeben, wird ihr Effekt an Eingabe- und Ausgabeframes übergeben, die Bilddaten in IDirect3DSurface-Objekten enthalten.
public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Cpu; } }
Hinweis
Wenn Sie MediaMemoryTypes.GpuAndCpu angeben, verwendet das System entweder GPU- oder Systemspeicher, je nachdem, was für die Pipeline effizienter ist. Bei Verwendung dieses Werts müssen Sie die ProcessFrame-Methode einchecken, um festzustellen, ob die an die Methode übergebene SoftwareBitmap oder IDirect3DSurface Daten enthält, und dann den Frame entsprechend verarbeiten.
TimeIndependent-Eigenschaft
Die TimeIndependent-Eigenschaft teilt dem System mit, ob der Effekt keine einheitliche Anzeigedauer erfordert. Wenn diese Option auf true gesetzt ist, kann das System Optimierungen verwenden, die die Effektleistung verbessern.
public bool TimeIndependent { get { return true; } }
SetProperties-Methode
Die Methode SetProperties ermöglicht es der Anwendung, die Ihren Effekt verwendet, die Effektparameter anzupassen. Eigenschaften werden als IPropertySet Karte der Eigenschaftsnamen und -werte übergeben.
private IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
this.configuration = configuration;
}
In diesem einfachen Beispiel werden die Pixel in jedem Videoframe entsprechend einem angegebenen Wert abgeblendet. Eine Eigenschaft wird deklariert und TryGetValue wird verwendet, um den von der aufrufenden Anwendung gesetzten Wert zu erhalten. Wurde kein Wert festgelegt, wird ein Standardwert von 0,5 verwendet.
public double FadeValue
{
get
{
object val;
if (configuration != null && configuration.TryGetValue("FadeValue", out val))
{
return (double)val;
}
return .5;
}
}
ProcessFrame-Methode
Die ProcessFrame-Methode ist der Ort, an dem Der Effekt die Bilddaten des Videos ändert. Die Methode wird einmal pro Frame aufgerufen und ein ProcessVideoFrameContext-Objekt übergeben. Dieses Objekt enthält ein VideoFrame-Eingabeobjekt, das den eingehenden Frame enthält, der verarbeitet werden soll, und ein VideoFrame-Ausgabeobjekt, in das Sie Bilddaten schreiben, die an den Rest der Videopipeline übergeben werden. Jedes dieser VideoFrame-Objekte verfügt über eine SoftwareBitmap-Eigenschaft und eine Direct3DSurface-Eigenschaft, aber welche dieser Objekte verwendet werden können, wird durch den Wert bestimmt, den Sie von der SupportedMemoryTypes-Eigenschaft zurückgegeben haben.
Dieses Beispiel zeigt eine einfache Implementierung der ProcessFrame-Methode mithilfe der Softwareverarbeitung. Weitere Informationen zum Arbeiten mit SoftwareBitmap-Objekten finden Sie unter Imaging. Ein Beispiel für die ProcessFrame-Implementierung mithilfe der Hardwareverarbeitung wird weiter unten in diesem Artikel gezeigt.
Für den Zugriff auf den Datenpuffer einer SoftwareBitmap ist COM-Interoperabilität erforderlich. Daher sollten Sie den System.Runtime.InteropServices-Namespace in die Effektklassendatei einschließen.
using System.Runtime.InteropServices;
Fügen Sie den folgenden Code im Namespace für den Effekt hinzu, um die Schnittstelle für den Zugriff auf den Bildpuffer zu importieren.
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}
Hinweis
Da diese Technik auf einen nativen, nicht verwalteten Bildpuffer zugreift, müssen Sie Ihr Projekt so konfigurieren, dass unsicherer Code zugelassen wird.
- Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf das Projekt "VideoEffectComponent", und wählen Sie "Eigenschaften" aus.
- Wählen Sie die Registerkarte Erstellen aus.
- Aktivieren Sie das Kontrollkästchen Unsicheren Code zulassen.
Jetzt können Sie die ProcessFrame-Methodenimplementierung hinzufügen. Zunächst ruft diese Methode ein BitmapBuffer-Objekt aus den Eingabe- und Ausgabesoftwarebitmaps ab. Beachten Sie, dass der Ausgangsrahmen zum Schreiben und der Eingangsrahmen zum Lesen geöffnet ist. Als nächstes wird eine IMemoryBufferReference für jeden Puffer durch Aufruf von CreateReference erstellt. Dann wird der tatsächliche Datenpuffer durch Casting der Objekte IMemoryBufferReference als die oben definierte COM-Interop-Schnittstelle IMemoryByteAccess erhalten und dann GetBuffer aufgerufen.
Nachdem Sie die Datenpuffer erhalten haben, können Sie nun aus dem Eingangspuffer lesen und in den Ausgangspuffer schreiben. Das Layout des Puffers wird durch Aufrufen von GetPlaneDescription abgerufen, das Informationen über die Breite, den Stride und den anfänglichen Offset des Puffers bereitstellt. Die Bits pro Pixel werden durch die zuvor mit der SetEncodingProperties-Methode festgelegten Codierungseigenschaften bestimmt. Die Pufferformatinformationen werden verwendet, um den Index für jedes Pixel im Puffer zu finden. Der Pixelwert aus dem Quellpuffer wird in den Zielpuffer kopiert, wobei die Farbwerte mit der FadeValue-Eigenschaft multipliziert werden, die für diesen Effekt definiert ist, um sie um den angegebenen Betrag abzublenden.
public unsafe void ProcessFrame(ProcessVideoFrameContext context)
{
using (BitmapBuffer buffer = context.InputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
using (BitmapBuffer targetBuffer = context.OutputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
{
using (var reference = buffer.CreateReference())
using (var targetReference = targetBuffer.CreateReference())
{
byte* dataInBytes;
uint capacity;
((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacity);
byte* targetDataInBytes;
uint targetCapacity;
((IMemoryBufferByteAccess)targetReference).GetBuffer(out targetDataInBytes, out targetCapacity);
var fadeValue = FadeValue;
// Fill-in the BGRA plane
BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);
for (int i = 0; i < bufferLayout.Height; i++)
{
for (int j = 0; j < bufferLayout.Width; j++)
{
byte value = (byte)((float)j / bufferLayout.Width * 255);
int bytesPerPixel = 4;
if (encodingProperties.Subtype != "ARGB32")
{
// If you support other encodings, adjust index into the buffer accordingly
}
int idx = bufferLayout.StartIndex + bufferLayout.Stride * i + bytesPerPixel * j;
targetDataInBytes[idx + 0] = (byte)(fadeValue * (float)dataInBytes[idx + 0]);
targetDataInBytes[idx + 1] = (byte)(fadeValue * (float)dataInBytes[idx + 1]);
targetDataInBytes[idx + 2] = (byte)(fadeValue * (float)dataInBytes[idx + 2]);
targetDataInBytes[idx + 3] = dataInBytes[idx + 3];
}
}
}
}
}
Implementieren der IBasicVideoEffect-Schnittstelle mithilfe der Hardwareverarbeitung
Das Erstellen eines benutzerdefinierten Videoeffekts mithilfe der Hardwareverarbeitung (GPU) ist fast identisch mit der Verwendung der Softwareverarbeitung, wie oben beschrieben. In diesem Abschnitt werden die wenigen Unterschiede in einem Effekt gezeigt, der die Hardwareverarbeitung verwendet. In diesem Beispiel wird die Win2D-Windows-Runtime-API verwendet. Weitere Informationen zur Verwendung von Win2D finden Sie in der Win2D-Dokumentation.
Führen Sie die folgenden Schritte aus, um das Win2D NuGet-Paket dem Projekt hinzuzufügen, das Sie erstellt haben, wie im Abschnitt "Hinzufügen eines benutzerdefinierten Effekts zu Ihrem App-Abschnitt am Anfang dieses Artikels" beschrieben.
So fügen Sie das Win2D NuGet-Paket zu Ihrem Effektprojekt hinzu
- Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf das Projekt "VideoEffectComponent", und wählen Sie "NuGet-Pakete verwalten" aus.
- Wählen Sie oben im Fenster die Registerkarte "Durchsuchen " aus.
- Geben Sie im Suchfeld Win2D ein.
- Wählen Sie "Win2D.uwp" und dann im rechten Bereich " Installieren" aus.
- Im Dialogfeld "Änderungen überprüfen" wird das zu installierende Paket angezeigt. Klicken Sie auf OK.
- Akzeptieren Sie die Paketlizenz.
Zusätzlich zu den Namespaces, die im grundlegenden Projektsetup enthalten sind, müssen Sie die folgenden Namespaces einschließen, die von Win2D bereitgestellt werden.
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas;
Da dieser Effekt GPU-Speicher für die Verwendung der Bilddaten verwendet, sollten Sie MediaMemoryTypes.Gpu aus der SupportedMemoryTypes-Eigenschaft zurückgeben.
public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }
Legen Sie die Codierungseigenschaften fest, die der Effekt mit der SupportedEncodingProperties-Eigenschaft unterstützt. Beim Arbeiten mit Win2D müssen Sie die ARGB32-Codierung verwenden.
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties {
get
{
var encodingProperties = new VideoEncodingProperties();
encodingProperties.Subtype = "ARGB32";
return new List<VideoEncodingProperties>() { encodingProperties };
}
}
Verwenden Sie die SetEncodingProperties-Methode, um ein neues Win2D CanvasDevice-Objekt aus dem an die Methode übergebenen IDirect3DDevice zu erstellen.
private CanvasDevice canvasDevice;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
}
Die SetProperties-Implementierung ist identisch mit dem vorherigen Beispiel für die Softwareverarbeitung. In diesem Beispiel wird eine BlurAmount-Eigenschaft verwendet, um einen Win2D-Weichzeichnereffekt zu konfigurieren.
private IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
this.configuration = configuration;
}
public double BlurAmount
{
get
{
object val;
if (configuration != null && configuration.TryGetValue("BlurAmount", out val))
{
return (double)val;
}
return 3;
}
}
Der letzte Schritt besteht darin, die ProcessFrame-Methode zu implementieren, die die Bilddaten tatsächlich verarbeitet.
Mithilfe von Win2D-APIs wird eine CanvasBitmap aus der Direct3DSurface-Eigenschaft des Eingabeframes erstellt. Ein CanvasRenderTarget wird aus dem Direct3DSurface des Ausgabeframes erstellt, und eine CanvasDrawingSession wird aus diesem Renderziel erstellt. Ein neues Win2D GaussianBlurEffect wird initialisiert, indem die BlurAmount-Eigenschaft verwendet wird, die unser Effekt über SetProperties verfügbar macht. Schließlich wird die CanvasDrawingSession.DrawImage-Methode aufgerufen, um die Eingabebitmap mit dem Weichzeichnereffekt auf das Renderziel zu zeichnen.
public void ProcessFrame(ProcessVideoFrameContext context)
{
using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
using (CanvasDrawingSession ds = renderTarget.CreateDrawingSession())
{
var gaussianBlurEffect = new GaussianBlurEffect
{
Source = inputBitmap,
BlurAmount = (float)BlurAmount,
Optimization = EffectOptimization.Speed
};
ds.DrawImage(gaussianBlurEffect);
}
}
Hinzufügen Ihres benutzerdefinierten Effekts zu Ihrer Anwendung
Wenn Sie Ihren Videoeffekt aus Ihrer App verwenden möchten, müssen Sie Ihrer App einen Verweis auf das Effektprojekt hinzufügen.
- Klicken Sie im Projektmappen-Explorer unter Ihrem Anwendungsprojekt mit der rechten Maustaste auf Referenzen und wählen Sie Referenz hinzufügen.
- Erweitern Sie die Registerkarte Projekte, wählen Sie Lösung und aktivieren Sie dann das Kontrollkästchen für den Namen Ihres Effektprojekts. In diesem Beispiel lautet der Name "VideoEffectComponent".
- Klicken Sie auf OK.
Hinzufügen des benutzerdefinierten Effekts zu einem Kameravideostream
Sie können einen einfachen Vorschaudatenstrom von der Kamera einrichten, indem Sie die Schritte im Artikel "Einfacher Kameravorschauzugriff" ausführen. Wenn Sie diese Schritte ausführen, erhalten Sie ein initialisiertes MediaCapture-Objekt , das für den Zugriff auf den Videostream der Kamera verwendet wird.
Um ihren benutzerdefinierten Videoeffekt einem Kameradatenstrom hinzuzufügen, erstellen Sie zuerst ein neues VideoEffectDefinition-Objekt , und übergeben Sie den Namespace und den Klassennamen für Ihren Effekt. Rufen Sie als Nächstes die AddVideoEffect-Methode des MediaCapture-Objekts auf, um den Effekt zum angegebenen Datenstrom hinzuzufügen. In diesem Beispiel wird der MediaStreamType.VideoPreview-Wert verwendet, um anzugeben, dass der Effekt dem Vorschaudatenstrom hinzugefügt werden soll. Wenn Ihre App die Videoaufnahme unterstützt, können Sie auch MediaStreamType.VideoRecord verwenden, um den Effekt zum Aufnahmedatenstrom hinzuzufügen. AddVideoEffect gibt ein IMediaExtension -Objekt zurück, das Ihren benutzerdefinierten Effekt darstellt. Sie können die SetProperties-Methode verwenden, um die Konfiguration für Den Effekt festzulegen.
Nachdem der Effekt hinzugefügt wurde, wird StartPreviewAsync aufgerufen, um den Vorschaudatenstrom zu starten.
var videoEffectDefinition = new VideoEffectDefinition("VideoEffectComponent.ExampleVideoEffect");
IMediaExtension videoEffect =
await mediaCapture.AddVideoEffectAsync(videoEffectDefinition, MediaStreamType.VideoPreview);
videoEffect.SetProperties(new PropertySet() { { "FadeValue", .25 } });
await mediaCapture.StartPreviewAsync();
Hinzufügen eines benutzerdefinierten Effekts zu einem Clip in einer MediaComposition
Allgemeine Anleitungen zum Erstellen von Medienkompositionen aus Videoclips finden Sie unter "Medienkompositionen" und "Bearbeiten". Der folgende Codeausschnitt zeigt die Erstellung einer einfachen Medienkomposition, die einen benutzerdefinierten Videoeffekt verwendet. Ein MediaClip-Objekt wird durch Aufrufen von CreateFromFileAsync erstellt, indem eine Videodatei übergeben wird, die vom Benutzer mit einem FileOpenPicker ausgewählt wurde, und der Clip wird einem neuen MediaComposition hinzugefügt. Als Nächstes wird ein neues VideoEffectDefinition-Objekt erstellt, wobei der Namespace und der Klassenname für den Effekt an den Konstruktor übergeben werden. Schließlich wird die Effektdefinition der VideoEffectDefinitions-Auflistung des MediaClip-Objekts hinzugefügt.
MediaComposition composition = new MediaComposition();
var clip = await MediaClip.CreateFromFileAsync(pickedFile);
composition.Clips.Add(clip);
var videoEffectDefinition = new VideoEffectDefinition("VideoEffectComponent.ExampleVideoEffect", new PropertySet() { { "FadeValue", .5 } });
clip.VideoEffectDefinitions.Add(videoEffectDefinition);