[Windows Phone 8] Comment manipuler la vidéo de l’appareil photo avec le SDK Nokia Imaging
Dans mon dernier article, nous voyions comment afficher le rendu de la caméra à l’intérieur d’une application Windows Phone. Nous avons vu que plusieurs solutions s’offraient à nous, dont la complexité pouvait varier. Mais à plus grande complexité, plus grande souplesse. En effet, dans le cas le plus avancé, nous pouvions manipuler les pixels de l’image de la frame courante afin de donner des « effets » à notre prévisualisation de la photo.
Voyons maintenant comment faire plus simple, plus performant et encore plus souple ! Ce petit miracle nous est permis grâce à l’utilisation du SDK Imaging. Il s’agit d’une librairie créée par Nokia dont le but est de manipuler des images. Bien qu’utilisable en C# (sous Windows Phone), ce SDK a été réalisé en C++ et offre donc des performances bien supérieure à ce que nous faisions avant. De plus toutes les opérations sont optimisées de façon à n’effectuer qu’un minimum de traitement.
Bien entendu, ce SDK étant de Nokia, cela ne signifie pas qu’il ne fonctionne pas sur un appareil d’une autre marque. Autrement dit, le SDK Imaging fonctionnera aussi bien sur HTC que sur un Nokia.
Enfin, avant de passer à la pratique, sachez que ce SDK offre une cinquantaine de filtres (ou effets) applicables à une image (ou dans notre cas à la prévisualisation de la caméra), et sont cumulables entre eux. La liste de ces filtres : http://developer.nokia.com/Resources/Library/Lumia/#!nokia-imaging-sdk.html.
Installer le SDK Imaging
Afin de bénéficier des formidables possibilités de ce SDK, vous pouvez l’installer via nugget (entre autre). Vous devrez ensuite redémarrer votre Visual Studio. Autre point, vous devrez compiler en ARM ou en x86 exclusivement (plus de Any CPU). Pour plus d’informations : http://developer.nokia.com/Resources/Library/Lumia/#!nokia-imaging-sdk/adding-libraries-to-the-project.html.
Comment brancher le SDK Imaging sur la caméra
Le SDK Imaging ne va pas se charger d’afficher le rendu de la caméra, ni même de prendre une photo. Donc une bonne partie de ce que nous avons vu dans l’article précédent reste valide. Le SDK se contente d’appliquer un filtre sur les frames, mais de façon beaucoup plus rapide que nous ne le faisions en C# (l’une des raisons étant que le traitement n’est pas fait en code managé). C’est pourquoi, si vous ne l’avez pas déjà fait, reportez-vous à l’article précédent afin de disposer d’un exemple pour implémenter un MediaStreamSource (source d’un MediaElement, permettant d’afficher le rendu de la caméra) et manipuler le PhotoCaptureDevice (la classe du Framework que nous utilisons pour récupérer les informations de la caméra et les frames).
Ainsi, dans notre implémentation du MediaStreamSource, que nous appelerons CameraStreamSource, nous pouvons modifier la frame renvoyée par le PhotoCaptureDevice avant de la mettre à disposition du MediaElement, dans la méthode GetSampleAsync. C’est donc ici que nous allons brancher le SDK Imaging.
Pour cela, nous devons disposer d’un objet de type CameraPreviewImageSource (Nokia.Graphics.Imaging.CameraPreviewImageSource) instancié avec un ICameraCaptureDevice. Cette interface est implémentée par notre PhotoCaptureDevice. C’est donc ce dernier que nous utiliserons :
IImageProvider _imageSource = new CameraPreviewImageSource(_photoCaptureDevice);
Ensuite, si vous êtes families du SDK Imaging, vous pouvez tout à fait continuer par vous-même. En effet, il suffit maintenant d’utiliser notre _imageSource comme tout autre provider comme le BitmapImageSource par exemple : Créer un FilterEffect et lui assigner une collection de filtre, récupérer l’image via le BitmapRenderer. Si cela ne vous parle pas, continuons !
Comment réinjecter l’image avec ces filtres dans le MediaStreamSource ?
Pour cela, il faut modifier notre implémentation du GetSampleAsync dans le CameraStreamSource :
#region [Pour Info] //_frameSize = new Windows.Foundation.Size(_frameWidth, _frameHeight); PhotoCaptureDevice camera = _camera; byte[] frameBuffer = _frameBuffer; Windows.Foundation.Size frameSize = _frameSize; MemoryStream frameStream = _frameStream; int frameBufferSize = _frameBufferSize; MediaStreamDescription videoStreamDescription = _videoStreamDescription; Dictionary<MediaSampleAttributeKeys, string> emptySampleDict = _emptySampleDict; #endregion if (camera == null) return; IBuffer nokiaBuffer = frameBuffer.AsBuffer(); var applyFiltersTask = ApplyFiltersAsync(nokiaBuffer, frameSize); applyFiltersTask.ContinueWith(_ => { if (frameStream == null) return; frameStream.Position = 0; var sample = new MediaStreamSample(videoStreamDescription, frameStream, 0, frameBufferSize, 0, emptySampleDict); ReportGetSampleCompleted(sample); });
Ici nous faisons appel à la méthode d’extension AsBuffer du Framework qui nous transforme notre tableau de bytes en IBuffer, objet manipulable par le SDK (ainsi toute modification sur le IBuffer sera répercuté sur le tableau de byte, lui-même référencé par le MemoryStream). Partant de là, nous appliquons nos filtres dans la méthode ApplyFiltersAsync, et nous y faisons le rendu dans le IBuffer. En effet, nous n’avons plus besoin d’appeler la méthode GetPreviewBufferArgb de notre PhotoCaptureDevice, puisque ceci est pris en charge par le CameraPreviewImageSource. Nous pouvons donc implémenter la méthode ApplyFiltersAsync ainsi :
private async Task ApplyFiltersAsync(IBuffer buffer, Windows.Foundation.Size frameSize) { var scanlineByteSize = (uint)frameSize.Width * 4; // 4 bytes per pixel in BGRA888 mode using (var bitmap = new Bitmap(frameSize, ColorMode.Bgra8888, scanlineByteSize, buffer)) if (_filterEffect != null) using (var renderer = new BitmapRenderer(_filterEffect, bitmap)) try { await renderer.RenderAsync(); } catch { } }
Ici, nous déclarons un bitmap, une image donc, dont la cible est notre IBuffer. Ainsi lorsque nous remplirons les données de notre frame dans le bitmap, le IBuffer les recevra. Ensuite nous utilisons un BitmapRenderer pour écrire ces données (notre frame) et appliquer les filtres dans bitmap. Le BitmapRenderer récupère la frame et les effets à appliquer grâce au _filterEffect, contenant lui-même une référence à notre _imageSource de tout à l’heure. Nous avons déjà définit ce dernier, il suffit d’y rajouter la définition du FilterEffect :
private IImageProvider _imageSource; private FilterEffect _filterEffect; private Size _frameSize; private MemoryStream _frameStream; private void PrepareImaging(int frameWidth, int frameHeight) { _frameSize = new Size(frameWidth, frameHeight); _frameStream = new MemoryStream(_frameBuffer); _imageSource = new CameraPreviewImageSource(_camera); _filterEffect = new FilterEffect(_imageSource) { Filters = new List<IFilter> { new GrayscaleFilter(), } }; }
Appelez PrepareImaging juste avant votre ReportOpenMediaCompleted (dans la méthode Initialize de CameraStreamSource par exemple).
En résumé, pour obtenir une frame, le MediaElement fait appel à notre MediaStreamSource (CameraStreamSource). Celle-ci appelle entre autre GetSampleAsync pour obtenir une preview de la camera (frame en basse définition). Pour récupérer cette preview, nous utilisons le CameraPreviewImageSource, sur lequel nous greffons un FilterEffect. Nous utilisons les méthodes de rendu du SDK Imaging sur le FilterEffect afin de remplir un IBuffer (et indirectement un MemoryStream associé).
Pensez donc que ces méthodes doivent être optimisées au maximum, car elles seront appelées très souvent à des intervalles de temps de l’ordre millième voir de la nano seconde. Il faut donc proscrire toute appel du type Debug.WriteLine, et autres, éviter les try catch si vous savez que le catch sera déclenché assez souvent, faites en sorte de ne pas trop déclarer de nouvelles variables, afin de faciliter la vie au Garbage Collector.
Dans notre exemple, nous appliquons un filtre noir et blanc (GrayScaleFilter) sur le rendu de la caméra.
Commentaires