Capture d’écran sur vidéo
Cet article explique comment encoder des images capturées à l'écran avec les API Windows.Graphics.Capture dans un fichier vidéo. Pour plus d'informations sur la capture d'images fixes à l'écran, consultez capture d'écran. Pour obtenir un exemple simple d'application de bout en bout qui utilise les concepts et techniques présentés dans cet article, consultez SimpleRecorder.
Vue d'ensemble du processus de capture vidéo
Cet article présente pas à pas un exemple d'application qui enregistre le contenu d'une fenêtre dans un fichier vidéo. Même s'il pourrait sembler que ce scénario nécessite de grandes quantités de code, la structure de haut niveau d'une application d'enregistrement d'écran est assez simple. Le processus de capture d'écran utilise trois fonctionnalités UWP principales :
- Les API Windows.GraphicsCapture se chargent du travail de saisie des pixels de l'écran. La classe GraphicsCaptureItem représente la fenêtre ou l'affichage capturé. GraphicsCaptureSession est utilisé pour démarrer et arrêter l'opération de capture. La classe Direct3D11CaptureFramePool conserve une mémoire tampon des images dans lesquelles sont copiés les contenus de l'écran.
- La classe MediaStreamSource reçoit les images capturées et génère un flux vidéo.
- La classe MediaTranscoder reçoit le flux généré par le MediaStreamSource et l'encode dans un fichier vidéo.
Le code présenté en exemple dans cet article peut être classé en plusieurs catégories de tâches :
- Initialisation - Cette tâche comprend la configuration des classes UWP décrites ci-dessus, l'initialisation des interfaces GDI, la sélection de la fenêtre à capturer et la configuration des paramètres d'encodage tels que la résolution et la fréquence d'images.
- Gestionnaires d'événements et threading - Le pilote principal de la boucle de capture principale est le MediaStreamSource, qui demande des images de façon périodique via l'événement SampleRequested. Cet exemple utilise des événements pour coordonner les demandes de nouvelles images entre ses différents composants. La synchronisation est importante pour que les images puissent être capturées et encodées simultanément.
- Copie des images - Les images sont copiées à partir de la mémoire tampon de capture dans une surface Direct3D séparée qui peut être transmise au MediaStreamSource pour éviter le remplacement de la ressource au moment de l'encodage. Des API Direct3D sont utilisées afin que cette opération de copie s'effectue rapidement.
À propos des API Direct3D
Comme indiqué ci-dessus, la copie de chaque image capturée est probablement la partie la plus complexe de la mise en œuvre présentée dans cet article. À bas niveau, cette opération est effectuée à l'aide de Direct3D. Pour cet exemple, nous utilisons la bibliothèque SharpDX pour effectuer les opérations Direct3D à partir de C#. Officiellement, cette bibliothèque n'est plus prise en charge, mais elle a été choisie en raison de ses performances en matière d'opérations de copie de bas niveau, qui sont particulièrement bien adaptées à ce scénario. Nous avons essayé de rendre les opérations Direct3D aussi distinctes que possible afin de vous faciliter le remplacement de votre propre code ou d'autres bibliothèques pour ces tâches.
Configuration de votre projet
Le code utilisé comme exemple dans cette procédure pas à pas a été créé à l'aide du modèle de projet C# Application vide (Windows universel) dans Visual Studio 2019. Pour utiliser les API Windows.Graphics.Capture dans votre application, vous devez inclure la fonctionnalité Graphics Capture dans le fichier Package.appxmanifest de votre projet. Cet exemple enregistre les fichiers vidéo générés dans la vidéothèque de l'appareil. Pour accéder à ce dossier, vous devez inclure la fonctionnalité Vidéothèque.
Pour installer le package NuGet SharpDX, dans Visual Studio, sélectionnez Gérer les packages NuGet. Dans l'onglet Parcourir, recherchez le package « SharpDX.Direct3D11 », puis cliquez sur Installer.
Notez que pour réduire la taille des listes de code dans cet article, le code de la procédure pas à pas ci-dessous omet les références d'espaces de noms explicites et la déclaration des variables membres de classe MainPage, dont le nom est précédé d'un trait de soulignement « _ ».
Configuration de l'encodage
La méthode SetupEncoding décrite dans cette section initialise certains des objets principaux qui seront utilisés pour capturer et encoder des images vidéo et configurer les paramètres d'encodage pour la vidéo capturée. Cette méthode peut être appelée par programmation ou en réponse à une interaction utilisateur comme un clic sur un bouton. La liste de code pour SetupEncoding est illustrée ci-dessous après la description des étapes d'initialisation.
Vérifiez la prise en charge de la capture. Avant de commencer le processus de capture, vous devez appeler GraphicsCaptureSession.IsSupported pour vous assurer que la fonctionnalité de capture d'écran est bien prise en charge sur l'appareil utilisé.
Initialisez les interfaces Direct3D. Cet exemple utilise Direct3D pour copier les pixels capturés à l'écran dans une texture encodée sous forme d'image vidéo. Les méthodes d'assistance utilisées pour initialiser les interfaces Direct3D, CreateD3DDevice et CreateSharpDXDevice, sont présentées ultérieurement dans cet article.
Initialisez un GraphicsCaptureItem. Un GraphicsCaptureItem représente un élément sur l'écran qui va être capturé. Il peut s'agir d'une fenêtre ou de l'écran entier. Permettez à l'utilisateur de sélectionner un élément à capturer en créant un GraphicsCapturePicker et en appelant PickSingleItemAsync.
Créez une texture de composition. Créez une ressource de texture et une vue cible de rendu associée qui sera utilisée pour copier chaque image vidéo. Cette texture ne peut pas être créée tant que GraphicsCaptureItem n'a pas été créée et que nous ne connaissons pas ses dimensions. Pour en savoir plus sur l'utilisation de cette texture de composition, veuillez vous reporter à la description de WaitForNewFrame. La méthode d'assistance pour la création de cette texture est également présentée ultérieurement dans cet article.
Créez un MediaEncodingProfile et un VideoStreamDescriptor. Une instance de la classe MediaStreamSource prend des images capturées à l'écran et les encode dans un flux vidéo. Ensuite, le flux vidéo est transcodé dans un fichier vidéo par la classe MediaTranscoder. Un VideoStreamDecriptor fournit des paramètres d'encodage, comme la résolution et la fréquence d'images, pour le MediaStreamSource. Les paramètres d'encodage de fichier vidéo pour le MediaTranscoder sont spécifiés à l'aide d'un MediaEncodingProfile. Notez que la taille retenue pour l'encodage vidéo ne doit pas nécessairement être identique à celle de la fenêtre capturée, mais pour préserver la simplicité de cet exemple, les paramètres d'encodage ont été codés en dur pour utiliser les dimensions réelles des éléments capturés.
Créez les objets MediaStreamSource et MediaTranscoder. Comme indiqué ci-dessus, l'objet MediaStreamSource encode des images individuelles dans un flux vidéo. Appelez le constructeur pour cette classe, en transmettant le MediaEncodingProfile issu de l'étape précédente. Établissez une valeur nulle pour la marge de temps et inscrivez des gestionnaires pour les événements Starting et SampleRequested, qui seront présentés ultérieurement dans cet article. Construisez ensuite une nouvelle instance de la classe MediaTranscoder et activez l'accélération matérielle.
Créez un fichier de sortie L'étape finale de cette méthode consiste à créer un fichier dans lequel la vidéo sera transcodée. Pour cet exemple, nous allons simplement créer un fichier portant un nom unique dans le dossier Vidéothèque de l'appareil. Notez que pour accéder à ce dossier, votre application doit spécifier la fonctionnalité « Vidéothèque » dans son manifeste. Une fois le fichier créé, ouvrez-le en mode lecture et écriture, puis transmettez le flux résultant à la méthode EncodeAsync qui ensuite s'affichera.
private async Task SetupEncoding()
{
if (!GraphicsCaptureSession.IsSupported())
{
// Show message to user that screen capture is unsupported
return;
}
// Create the D3D device and SharpDX device
if (_device == null)
{
_device = Direct3D11Helpers.CreateD3DDevice();
}
if (_sharpDxD3dDevice == null)
{
_sharpDxD3dDevice = Direct3D11Helpers.CreateSharpDXDevice(_device);
}
try
{
// Let the user pick an item to capture
var picker = new GraphicsCapturePicker();
_captureItem = await picker.PickSingleItemAsync();
if (_captureItem == null)
{
return;
}
// Initialize a blank texture and render target view for copying frames, using the same size as the capture item
_composeTexture = Direct3D11Helpers.InitializeComposeTexture(_sharpDxD3dDevice, _captureItem.Size);
_composeRenderTargetView = new SharpDX.Direct3D11.RenderTargetView(_sharpDxD3dDevice, _composeTexture);
// This example encodes video using the item's actual size.
var width = (uint)_captureItem.Size.Width;
var height = (uint)_captureItem.Size.Height;
// Make sure the dimensions are are even. Required by some encoders.
width = (width % 2 == 0) ? width : width + 1;
height = (height % 2 == 0) ? height : height + 1;
var temp = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p);
var bitrate = temp.Video.Bitrate;
uint framerate = 30;
_encodingProfile = new MediaEncodingProfile();
_encodingProfile.Container.Subtype = "MPEG4";
_encodingProfile.Video.Subtype = "H264";
_encodingProfile.Video.Width = width;
_encodingProfile.Video.Height = height;
_encodingProfile.Video.Bitrate = bitrate;
_encodingProfile.Video.FrameRate.Numerator = framerate;
_encodingProfile.Video.FrameRate.Denominator = 1;
_encodingProfile.Video.PixelAspectRatio.Numerator = 1;
_encodingProfile.Video.PixelAspectRatio.Denominator = 1;
var videoProperties = VideoEncodingProperties.CreateUncompressed(MediaEncodingSubtypes.Bgra8, width, height);
_videoDescriptor = new VideoStreamDescriptor(videoProperties);
// Create our MediaStreamSource
_mediaStreamSource = new MediaStreamSource(_videoDescriptor);
_mediaStreamSource.BufferTime = TimeSpan.FromSeconds(0);
_mediaStreamSource.Starting += OnMediaStreamSourceStarting;
_mediaStreamSource.SampleRequested += OnMediaStreamSourceSampleRequested;
// Create our transcoder
_transcoder = new MediaTranscoder();
_transcoder.HardwareAccelerationEnabled = true;
// Create a destination file - Access to the VideosLibrary requires the "Videos Library" capability
var folder = KnownFolders.VideosLibrary;
var name = DateTime.Now.ToString("yyyyMMdd-HHmm-ss");
var file = await folder.CreateFileAsync($"{name}.mp4");
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
await EncodeAsync(stream);
}
catch (Exception ex)
{
return;
}
}
Démarrer l'encodage
Une fois les objets principaux initialisés, la méthode EncodeAsync est implémentée pour lancer l'opération de capture. Après avoir tout d'abord vérifié qu'aucun enregistrement n'est en cours, elle appelle la méthode d'assistance StartCapture pour commencer à capturer des images à partir de l'écran. Cette méthode est présentée ultérieurement dans cet article. Ensuite, PrepareMediaStreamSourceTranscodeAsync est appelé pour que le MediaTranscoder se prépare à transcoder le flux vidéo produit par l'objet MediaStreamSource vers le flux de fichiers de sortie, à l'aide du profil d'encodage que nous avons créé dans la section précédente. Une fois que le transcodeur a été préparé, appelez TranscodeAsync pour démarrer le transcodage. Pour en savoir plus sur l'utilisation du MediaTranscoder, consultez Transcoder des fichiers multimédias.
private async Task EncodeAsync(IRandomAccessStream stream)
{
if (!_isRecording)
{
_isRecording = true;
StartCapture();
var transcode = await _transcoder.PrepareMediaStreamSourceTranscodeAsync(_mediaStreamSource, stream, _encodingProfile);
await transcode.TranscodeAsync();
}
}
Gérer les événements MediaStreamSource
L'objet MediaStreamSource prend les images que nous capturons à l'écran et les transforme en un flux vidéo qu'il est possible d'enregistrer dans un fichier à l'aide du MediaTranscoder. Nous transmettons les images au MediaStreamSource via des gestionnaires pour les événements de l'objet.
L'événement SampleRequested est déclenché lorsque le MediaStreamSource est prêt pour une nouvelle image vidéo. Après s'être assuré que l'enregistrement est bien en cours, la méthode d'assistance WaitForNewFrame est appelée pour recevoir une nouvelle image capturée à l'écran. Cette méthode, présentée ultérieurement dans cet article, fournit un objet ID3D11Surface contenant l'image capturée. Pour cet exemple, nous encapsulons l'interface IDirect3DSurface dans une classe d'assistance qui stocke également l'heure système à laquelle l'image a été capturée. L'image et l'heure système sont transmises à la méthode de fabrique MediaStreamSample.CreateFromDirect3D11Surface et le MediaStreamSample résultant est établi sur la propriété MediaStreamSourceSampleRequest.Sample du MediaStreamSourceSampleRequestedEventArgs. L'image capturée est ainsi fournie au MediaStreamSource.
private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
if (_isRecording && !_closed)
{
try
{
using (var frame = WaitForNewFrame())
{
if (frame == null)
{
args.Request.Sample = null;
Stop();
Cleanup();
return;
}
var timeStamp = frame.SystemRelativeTime;
var sample = MediaStreamSample.CreateFromDirect3D11Surface(frame.Surface, timeStamp);
args.Request.Sample = sample;
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
Debug.WriteLine(e.StackTrace);
Debug.WriteLine(e);
args.Request.Sample = null;
Stop();
Cleanup();
}
}
else
{
args.Request.Sample = null;
Stop();
Cleanup();
}
}
Dans le gestionnaire de l'événement Starting , nous appelons WaitForNewFrame, mais seule l'heure système de capture de l'image est transmise à la méthode MediaStreamSourceStartingRequest.SetActualStartPosition. Elle sera utilisée par MediaStreamSource pour encoder correctement la synchronisation des images suivantes.
private void OnMediaStreamSourceStarting(MediaStreamSource sender, MediaStreamSourceStartingEventArgs args)
{
using (var frame = WaitForNewFrame())
{
args.Request.SetActualStartPosition(frame.SystemRelativeTime);
}
}
Commencer la capture
La méthode StartCapture indiquée dans cette étape est appelée à partir de la méthode d'assistance EncodeAsync présentée dans une étape précédente. Cette méthode initialise tout d'abord un ensemble d'objets d'événements utilisés pour contrôler le flux de l'opération de capture.
- La classe d'assistance _multithread encapsule l'objet Multithread de la bibliothèque SharpDX, qui sera utilisé pour veiller à ce qu'aucun autre thread n'accède à la texture SharpDX pendant sa copie.
- _frameEvent est utilisé pour signaler qu'une nouvelle image a été capturée et peut être transmise au MediaStreamSource
- _closedEvent signale que l'enregistrement s'est arrêté et qu'aucune autre image n'est à attendre.
_frameEvent et _closedEvent sont ajoutés à un tableau afin que nous puissions attendre l'un ou l'autre dans la boucle de capture.
Le reste de la méthode StartCapture configure les API Windows.Graphics.Capture qui réaliseront la capture d'écran proprement dite. Tout d'abord, un événement est inscrit pour l'événement CaptureItem.Closed. Un Direct3D11CaptureFramePool est ensuite créé pour permettre à plusieurs images capturées d'être mises en mémoire tampon à la fois. La méthode CreateFreeThreaded est utilisée pour créer le pool d'images afin que l'événement FrameArrived soit appelé sur le thread de travail du pool plutôt que sur le thread principal de l'application. Ensuite, un gestionnaire est inscrit pour l'événement FrameArrived. Enfin, un GraphicsCaptureSession est créé pour l'élément CaptureItem sélectionné et la capture d'images est lancée en appelant StartCapture.
public void StartCapture()
{
_multithread = _sharpDxD3dDevice.QueryInterface<SharpDX.Direct3D11.Multithread>();
_multithread.SetMultithreadProtected(true);
_frameEvent = new ManualResetEvent(false);
_closedEvent = new ManualResetEvent(false);
_events = new[] { _closedEvent, _frameEvent };
_captureItem.Closed += OnClosed;
_framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
_device,
DirectXPixelFormat.B8G8R8A8UIntNormalized,
1,
_captureItem.Size);
_framePool.FrameArrived += OnFrameArrived;
_session = _framePool.CreateCaptureSession(_captureItem);
_session.StartCapture();
}
Gérer les événements de capture graphique
À l'étape précédente, nous avons inscrit deux gestionnaires pour les événements de capture graphique et configuré plusieurs événements destinés à faciliter la gestion du flux de la boucle de capture.
L'événement FrameArrived est déclenché lorsqu'une nouvelle image capturée est disponible dans Direct3D11CaptureFramePool. Dans le gestionnaire de cet événement, appelez TryGetNextFrame sur l'expéditeur pour recevoir l'image capturée suivante. Une fois l'image récupérée, nous configurons le _frameEvent de façon à ce que notre boucle de capture sache qu'une nouvelle image est disponible.
private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
{
_currentFrame = sender.TryGetNextFrame();
_frameEvent.Set();
}
Dans le gestionnaire d'événements Closed, nous signalons le _closedEvent de façon à ce que la boucle de capture sache quand s'arrêter.
private void OnClosed(GraphicsCaptureItem sender, object args)
{
_closedEvent.Set();
}
Attendre de nouvelles images
La méthode d'assistance WaitForNewFrame décrite dans cette section est le siège des tâches les plus lourdes associées à la boucle de capture. N'oubliez pas que cette méthode est appelée à partir du gestionnaire d'événements OnMediaStreamSourceSampleRequested chaque fois que MediaStreamSource est prêt pour l'ajout d'une nouvelle image au flux vidéo. À un niveau élevé, cette fonction copie simplement chaque image vidéo capturée à l'écran d'une surface Direct3D vers une autre afin qu'elle puisse être transmise au MediaStreamSource pour l'encodage tandis qu'une nouvelle image est capturée. Cet exemple utilise la bibliothèque SharpDX pour effectuer l'opération de copie proprement dite.
Avant d'attendre une nouvelle image, la méthode supprime toutes celles précédemment stockées dans la variable de classe, _currentFrame, et réinitialise le _frameEvent. Ensuite, la méthode attend que l'événement _frameEvent ou _closedEvent soit signalé. Si l'événement _closedEvent est établi, l'application appelle une méthode d'assistance pour éliminer proprement les ressources de capture. Cette méthode est présentée ultérieurement dans cet article.
Si l'événement _frameEvent est établi, cela signifie que le gestionnaire d'événements FrameArrived défini à l'étape précédente a été appelé, c'est pourquoi nous commençons le processus de copie des données des images capturées dans une surface Direct3D 11 qui sera transmise au MediaStreamSource.
Cet exemple utilise une classe d'assistance, SurfaceWithInfo, qui nous permet simplement de transmettre l'image vidéo et l'heure système qui lui est associée, toutes deux requises par le MediaStreamSource, en tant qu'objet unique. La première étape du processus de copie d'images consiste à instancier cette classe et à configurer l'heure système.
Les étapes suivantes correspondent à la partie de cet exemple qui s'appuie spécifiquement sur la bibliothèque SharpDX. Les fonctions d'assistance utilisées ici sont définies à la fin de cet article. Nous utilisons tout d'abord le MultiThreadLock pour veiller à ce qu'aucun autre thread n'accède à la mémoire tampon d'images vidéo tandis que nous effectuons la copie. Ensuite, nous appelons la méthode d’assistance CreateSharpDXTexture2D pour créer un objet Texture2D SharpDX à partir de l’image vidéo. Celui-ci sera la texture source pour l’opération de copie.
Ensuite, nous effectuons une copie de l’objet Texture2D créé à l’étape précédente dans la texture de composition que nous avons créée auparavant dans le processus. Cette texture de composition joue un rôle d'échange de tampons afin que le processus d’encodage puisse opérer sur les pixels pendant que l'image suivante est capturée. Pour effectuer la copie, nous annulons la vue cible de rendu associée à la texture de composition, puis nous définissons la région de la texture que nous voulons copier (ici, la texture entière) et nous appelons CopySubresourceRegion pour effectuer la copie proprement dite des pixels dans la texture de composition.
Nous créons une copie de la description de la texture à utiliser lorsque nous créons notre texture cible, mais en la modifiant de façon à configurer BindFlags sur RenderTarget de façon à ce que la nouvelle texture dispose d'un accès en écriture. La configuration de CpuAccessFlags sur None permet au système d’optimiser l’opération de copie. La description de texture est utilisée pour créer une nouvelle ressource de texture, dans laquelle est copiée la ressource de texture de composition en appelant CopyResource. Enfin, CreateDirect3DSurfaceFromSharpDXTexture est appelé pour créer l’objet IDirect3DSurface qui est obtenu par cette méthode.
public SurfaceWithInfo WaitForNewFrame()
{
// Let's get a fresh one.
_currentFrame?.Dispose();
_frameEvent.Reset();
var signaledEvent = _events[WaitHandle.WaitAny(_events)];
if (signaledEvent == _closedEvent)
{
Cleanup();
return null;
}
var result = new SurfaceWithInfo();
result.SystemRelativeTime = _currentFrame.SystemRelativeTime;
using (var multithreadLock = new MultithreadLock(_multithread))
using (var sourceTexture = Direct3D11Helpers.CreateSharpDXTexture2D(_currentFrame.Surface))
{
_sharpDxD3dDevice.ImmediateContext.ClearRenderTargetView(_composeRenderTargetView, new SharpDX.Mathematics.Interop.RawColor4(0, 0, 0, 1));
var width = Math.Clamp(_currentFrame.ContentSize.Width, 0, _currentFrame.Surface.Description.Width);
var height = Math.Clamp(_currentFrame.ContentSize.Height, 0, _currentFrame.Surface.Description.Height);
var region = new SharpDX.Direct3D11.ResourceRegion(0, 0, 0, width, height, 1);
_sharpDxD3dDevice.ImmediateContext.CopySubresourceRegion(sourceTexture, 0, region, _composeTexture, 0);
var description = sourceTexture.Description;
description.Usage = SharpDX.Direct3D11.ResourceUsage.Default;
description.BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget;
description.CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None;
description.OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None;
using (var copyTexture = new SharpDX.Direct3D11.Texture2D(_sharpDxD3dDevice, description))
{
_sharpDxD3dDevice.ImmediateContext.CopyResource(_composeTexture, copyTexture);
result.Surface = Direct3D11Helpers.CreateDirect3DSurfaceFromSharpDXTexture(copyTexture);
}
}
return result;
}
Arrêter la capture et éliminer proprement les ressources
La méthode Stop permet d’arrêter l’opération de capture. Votre application peut l'appeler par programmation ou en réponse à une interaction utilisateur, comme un clic sur un bouton. Cette méthode établit simplement l'événement _closedEvent. La méthode WaitForNewFrame définie dans les étapes précédentes recherche cet événement et, s'il est établi, arrête l’opération de capture.
private void Stop()
{
_closedEvent.Set();
}
La méthode Cleanup est utilisée pour éliminer proprement les ressources qui ont été créées pendant l’opération de copie, dont notamment :
- l'objet Direct3D11CaptureFramePool utilisé par la session de capture ;
- le GraphicsCaptureSession et le GraphicsCaptureItem ;
- les appareils Direct3D et SharpDX ;
- la texture SharpDX et la vue cible de rendu utilisées dans l’opération de copie ;
- le Direct3D11CaptureFrame, utilisé pour stocker l'image en cours.
private void Cleanup()
{
_framePool?.Dispose();
_session?.Dispose();
if (_captureItem != null)
{
_captureItem.Closed -= OnClosed;
}
_captureItem = null;
_device = null;
_sharpDxD3dDevice = null;
_composeTexture?.Dispose();
_composeTexture = null;
_composeRenderTargetView?.Dispose();
_composeRenderTargetView = null;
_currentFrame?.Dispose();
}
Classes d’assistance et d'encapsulation
Les classes d’assistance suivantes ont été définies pour faciliter l'utilisation du code d'exemple de cet article.
La classe d'assistance MultithreadLock encapsule la classe Multithread SharpDX qui veille à ce qu'aucun autre thread n'accède aux ressources de texture pendant sa copie.
class MultithreadLock : IDisposable
{
public MultithreadLock(SharpDX.Direct3D11.Multithread multithread)
{
_multithread = multithread;
_multithread?.Enter();
}
public void Dispose()
{
_multithread?.Leave();
_multithread = null;
}
private SharpDX.Direct3D11.Multithread _multithread;
}
SurfaceWithInfo est utilisé pour associer un IDirect3DSurface à un SystemRelativeTime représentant une trame capturée et l’heure à laquelle elle a été capturée, respectivement.
public sealed class SurfaceWithInfo : IDisposable
{
public IDirect3DSurface Surface { get; internal set; }
public TimeSpan SystemRelativeTime { get; internal set; }
public void Dispose()
{
Surface?.Dispose();
Surface = null;
}
}
API d’assistance Direct3D et SharpDX
Les API d'assistance suivantes sont définies pour simplifier la création des ressources Direct3D et SharpDX. L'explication détaillée de ces technologies dépasse le cadre de cet article, mais le code fourni ici vous permet de l'utiliser comme exemple dans la procédure pas à pas.
[ComImport]
[Guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
interface IDirect3DDxgiInterfaceAccess
{
IntPtr GetInterface([In] ref Guid iid);
};
public static class Direct3D11Helpers
{
internal static Guid IInspectable = new Guid("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90");
internal static Guid ID3D11Resource = new Guid("dc8e63f3-d12b-4952-b47b-5e45026a862d");
internal static Guid IDXGIAdapter3 = new Guid("645967A4-1392-4310-A798-8053CE3E93FD");
internal static Guid ID3D11Device = new Guid("db6f6ddb-ac77-4e88-8253-819df9bbf140");
internal static Guid ID3D11Texture2D = new Guid("6f15aaf2-d208-4e89-9ab4-489535d34f9c");
[DllImport(
"d3d11.dll",
EntryPoint = "CreateDirect3D11DeviceFromDXGIDevice",
SetLastError = true,
CharSet = CharSet.Unicode,
ExactSpelling = true,
CallingConvention = CallingConvention.StdCall
)]
internal static extern UInt32 CreateDirect3D11DeviceFromDXGIDevice(IntPtr dxgiDevice, out IntPtr graphicsDevice);
[DllImport(
"d3d11.dll",
EntryPoint = "CreateDirect3D11SurfaceFromDXGISurface",
SetLastError = true,
CharSet = CharSet.Unicode,
ExactSpelling = true,
CallingConvention = CallingConvention.StdCall
)]
internal static extern UInt32 CreateDirect3D11SurfaceFromDXGISurface(IntPtr dxgiSurface, out IntPtr graphicsSurface);
public static IDirect3DDevice CreateD3DDevice()
{
return CreateD3DDevice(false);
}
public static IDirect3DDevice CreateD3DDevice(bool useWARP)
{
var d3dDevice = new SharpDX.Direct3D11.Device(
useWARP ? SharpDX.Direct3D.DriverType.Software : SharpDX.Direct3D.DriverType.Hardware,
SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport);
IDirect3DDevice device = null;
// Acquire the DXGI interface for the Direct3D device.
using (var dxgiDevice = d3dDevice.QueryInterface<SharpDX.DXGI.Device3>())
{
// Wrap the native device using a WinRT interop object.
uint hr = CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.NativePointer, out IntPtr pUnknown);
if (hr == 0)
{
device = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DDevice;
Marshal.Release(pUnknown);
}
}
return device;
}
internal static IDirect3DSurface CreateDirect3DSurfaceFromSharpDXTexture(SharpDX.Direct3D11.Texture2D texture)
{
IDirect3DSurface surface = null;
// Acquire the DXGI interface for the Direct3D surface.
using (var dxgiSurface = texture.QueryInterface<SharpDX.DXGI.Surface>())
{
// Wrap the native device using a WinRT interop object.
uint hr = CreateDirect3D11SurfaceFromDXGISurface(dxgiSurface.NativePointer, out IntPtr pUnknown);
if (hr == 0)
{
surface = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DSurface;
Marshal.Release(pUnknown);
}
}
return surface;
}
internal static SharpDX.Direct3D11.Device CreateSharpDXDevice(IDirect3DDevice device)
{
var access = (IDirect3DDxgiInterfaceAccess)device;
var d3dPointer = access.GetInterface(ID3D11Device);
var d3dDevice = new SharpDX.Direct3D11.Device(d3dPointer);
return d3dDevice;
}
internal static SharpDX.Direct3D11.Texture2D CreateSharpDXTexture2D(IDirect3DSurface surface)
{
var access = (IDirect3DDxgiInterfaceAccess)surface;
var d3dPointer = access.GetInterface(ID3D11Texture2D);
var d3dSurface = new SharpDX.Direct3D11.Texture2D(d3dPointer);
return d3dSurface;
}
public static SharpDX.Direct3D11.Texture2D InitializeComposeTexture(
SharpDX.Direct3D11.Device sharpDxD3dDevice,
SizeInt32 size)
{
var description = new SharpDX.Direct3D11.Texture2DDescription
{
Width = size.Width,
Height = size.Height,
MipLevels = 1,
ArraySize = 1,
Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
SampleDescription = new SharpDX.DXGI.SampleDescription()
{
Count = 1,
Quality = 0
},
Usage = SharpDX.Direct3D11.ResourceUsage.Default,
BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget,
CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None,
OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None
};
var composeTexture = new SharpDX.Direct3D11.Texture2D(sharpDxD3dDevice, description);
using (var renderTargetView = new SharpDX.Direct3D11.RenderTargetView(sharpDxD3dDevice, composeTexture))
{
sharpDxD3dDevice.ImmediateContext.ClearRenderTargetView(renderTargetView, new SharpDX.Mathematics.Interop.RawColor4(0, 0, 0, 1));
}
return composeTexture;
}
}