Real-time camera viewfinder filters in Native code
This article explains how to create real-time camera filters for Windows Phone 8, using native code (C++).
Article Metadata
Code Example
Tested with
Compatibility
Article
Contents |
Introduction
One of the big new features of Windows Phone 8 SDK is the support for C and C++, also known as native code support. In this article, we will have a brief look at how one can exploit that support to create real-time filters for the camera. For simplicity's sake, the example will implement a simple gray filter, that will convert camera input on the fly and show the result onscreen.
The demo application will look like this:
.
Why native filters?
Microsoft has published a very similar example, where they do live conversion of the camera viewfinder images to grayscale. The example was written for Windows Phone 7 (you can download it here), but it also works well in WP8. This works well, but the gray filter is quite simple; more complicated filters will require more computation, and the CPU is quickly maxed out when we try to process the camera input at several frames per second. The speed gain by going closer to the metal may be needed for more complex algorithms. Also, you might already have your own image filters written in C and/or C++ for other platforms, that you can reuse without converting them to C#. Finally, as we will see in other wiki entries, the native side opens further optimization possibilities, like using DirectX or the ARM Neon instruction set.
Setting up the viewfinder
Our UI will be XAML based. The UI will control everything, while the C++ side is rather dumb, simply executing the filtering when asked to. Let's first create the projects for both the XAML and the C++ components:
- Start by creating a new project, of type Windows Phone App. You will find the template under the Visual C#/Windows Phone category. That's where our UI will be coded.
- Add a new project to your solution, of type Windows Phone Runtime Component. That template is under the Visual C++/Windows Phone category. That will become our image filter.
We then add a live camera stream to our UI. That is easily done by:
- In your XAML, define a rectangle that will be painted using a VideoBrush:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Rectangle Width="640" Height="480" Canvas.ZIndex="1">
<Rectangle.Fill>
<VideoBrush x:Name="viewfinderBrush" />
</Rectangle.Fill>
</Rectangle>
</Grid>
- In the page loaded event, create a PhotoCaptureDevice, and set it as the source of the VideoBrush:
Windows.Foundation.Size resolution = new Windows.Foundation.Size(640, 480);
m_camera = await PhotoCaptureDevice.OpenAsync(CameraSensorLocation.Back, resolution);
ViewfinderBrush.SetSource(m_camera);
By now, with these 10 lines of codes, you should have an application with a functional camera! Note that in the last step, we use the Windows PRT class Windows.Phone.Media.Capture.PhotoCaptureDevice which is new to Windows Phone 8. In WP7, one would have to use the Silverlight/.NET class Microsoft.Devices.PhotoCamera. Because it's a Win PRT class, it can be accessed by both managed and native code; we will soon take advantage of that possibility.
Displaying the filtered frames
The VideoBrush is easy to use but it doesn't offer us a way to get in-between the camera and the brush to apply our filter. We need to find another way to display the modified frames coming from the camera. Several strategies are possible, but the strategy chosen must have a reasonable camera lag (the time between the photons enters the camera until the scene is displayed on the screen) as well as a decent frame rate. Let's have a quick look at 3 possible strategies:
- Using an Image control, with WriteableBitmap as a source.
- Using a MediaElement control, with a custom MediaStreamSource as a source.
- Using a DirectX texture.
Using an Image control
This is the strategy used in the How to: Work with Grayscale in a Camera Application for Windows Phone MSDN sample. The UI control defined in XAML is:
<Image x:Name="MyCameraViewfiner" Width="640" Height="480">The source of the Image} is set to a WriteableBitmap. When the frames from the camera have been filtered, the resulting array of pixels is copied into to that WriteableBitmap.
Deployment.Current.Dispatcher.BeginInvoke(delegate() // Switch to UI thread
{
// Copy to WriteableBitmap.
ARGBPx.CopyTo(wb.Pixels, 0);
wb.Invalidate();
});
The problem with this is the switch to UI thread (Deployment.Current.Dispatcher.BeginInvoke). The switch is required so that the application does not update the bitmap while the UI thread is doing something with it. Switching between threads is quite slow, and the lag associated with that solution was too high for the application.
Using a MediaElement
The MediaElement is 'the' UI control that is used for media playback, videos or audio, streamed media or from a local file. It takes care of all the buffering, audio sync and displaying logic and it's highly optimized. By defining a custom MediaStreamSource, we can feed the filtered camera frames to the MediaElement that will take care of displaying them properly.
<MediaElement x:Name="MyCameraMediaElement"
IsHitTestVisible="False"
Margin="4" Width="640" Height="480" />
Defining your own MediaStreamSource requires a little bit of work, but it's all worth it. The lag with this solution is small, and the frame rate we can achieve is pretty good. This is the solution we will take into use.
Using a DirectX texture
When it comes to camera lag and performance, this is probably the best solution. However DirectX has a fairly steep learning curve, and to keep things simple we will not use it. Look over this article if you're interested in using DirectX.
Our own MediaStreamSource
To feed video frames to the MediaElement we need to define our custom MediaStreamSource. Pete Brown walks us through how this should be done in his blog. Check it out for more details, here we will only go through the big lines.
Our MediaStreamSource will define a single stream of type video (RGBA). Since our real source for video data is the camera, we have a source of infinite length and that can't be seeked. Our initialization will look like this:
mediaStreamAttributes[MediaStreamAttributeKeys.VideoFourCC] = "RGBA";
mediaStreamAttributes[MediaStreamAttributeKeys.Width] = _frameWidth.ToString();
mediaStreamAttributes[MediaStreamAttributeKeys.Height] = _frameHeight.ToString();
mediaStreamDescriptions.Add(_videoStreamDescription);
// a zero timespan is an infinite video
mediaSourceAttributes[MediaSourceAttributesKeys.Duration] =
TimeSpan.FromSeconds(0).Ticks.ToString(CultureInfo.InvariantCulture);
// Can't seek.
mediaSourceAttributes[MediaSourceAttributesKeys.CanSeek] = false.ToString();
Whenever the MediaElement decides it needs a new video frame, it will call the GetSampleAsync() method from our MediaStreamSource.
We will implement the following sequence diagram :
To get the data of the frames coming from the camera, we will have to call to the platform functionality Windows::Phone::Media::Capture::ICameraCaptureDevice::GetPreviewBufferArgb. Let's look at its definition:
void GetPreviewBufferArgb(Platform::WriteOnlyArray<int, 1U>^ pixels)
That method fills an array of our choice with the camera data. That's a copy operation we can't avoid. The buffer is of type Platform::WriteOnlyArray, which according to the MSDN documentation, is to be used when the caller passes an array for the method to fill. We will also use that buffer type to communicate between our managed component and our native component.
The public interface of our native component will be:
public ref class WindowsPhoneRuntimeComponent sealed
{
public:
WindowsPhoneRuntimeComponent();
void Initialize(Windows::Phone::Media::Capture::PhotoCaptureDevice^ captureDevice);
void NewViewfinderFrame( Platform::WriteOnlyArray<int,1U>^ inputBuffer,
Platform::WriteOnlyArray<uint8,1U>^ outputBuffer);
...
};
On the managed side, we allocate the inputBuffer and the outputBuffer when the application is initialized, and reuse those buffers for each frame:
_cameraData = new int[_frameWidth * _frameHeight];
_frameBufferSize = _frameWidth * _frameHeight * _framePixelSize;
_cameraFilteredData = new byte[_frameBufferSize]
Note that for this example, my data buffer is in RGBA format. It's a format easy to handle, and the most familiar for most of us. However, it is not very efficient in terms of size and image manipulation performance. Using YUV format/color space would make more sense.
That covers the big lines of the data handling of the application. I skipped the not-so-interesting code, get the source code of the full project from the link in top right corner of this page. The last thing to do is the filtering itself.
The filtering in C++
For the filtering, I took the code from Nils Pipenbrinck's excellent blog entry on Neon optimization.
void WindowsPhoneRuntimeComponent::ConvertToGrayOriginal( Platform::WriteOnlyArray<int,1U>^ frameData)
{
uint8 * src = (uint8 *) frameData->Data;
uint8 * dest = (uint8 *) frameData->Data;
int n = frameData->Length;
int i;
for (i=0; i<n; i++)
{
int r = *src++; // load red
int g = *src++; // load green
int b = *src++; // load blue
src++; //Alpha
// build weighted average:
int y = (r*77)+(g*151)+(b*28);
// undo the scale by 256 and write to memory:
*dest++ = (y>>8);
*dest++ = (y>>8);
*dest++ = (y>>8);
dest++;
}
}
If you look in the project code, you will find the same filter, but optimized with the ARM Neon instruction set. I'll let you pick the one you prefer.
Wrapping it up
Hopefully this short article will help you getting started writing native filters with Windows Phone 8. Remember to always keep the performance in mind, these types of applications are very CPU intensive. Using C++ for your filters is one way to improve the performance. If the performance gain is not enough for your use cases, you might have a look at hooking your camera directly into DirectX, or optimizing your filter with Neon instructions.
Source code
The source code is maintained in Nokia Project. Look for the zip file at the bottom of the page.



Contents
Chintandave er - Thanks.
Hi Mansewiz, Thanks for this nice article.
I have sub-edited it and added category Windows phone 8 and did some code formatting.
Whenever you are done with this article, remove draft category. Once you finish this article, we will further sub-edit it and give you some suggestions after review.
Thanks
Chintan Dave.Chintandave er 11:04, 27 November 2012 (EET)
Mansewiz - Not a draft anymore
Hi Dave, I'm pretty satisfied with what I have right now, so I removed the draft category. Looking forward review suggestions!
ThanksMansewiz 10:57, 2 December 2012 (EET)
TonyTam - Slow performance
Nice article but why there is only 9 fps on my Lumia 920 with Neon implementation.TonyTam 17:01, 4 April 2013 (EEST)
Mansewiz - Slow performance.
@TonyTam: You're right, the version in project is not really fast.
There is a simple fix that should increase significantly the perf, change the line from CameraStreamSource.cs from :
to
I will check the fix in the project soon. Thanks for pointing this out!Mansewiz 10:28, 8 April 2013 (EEST)
TonyTam - Slow performance
Thanks your quick reply! I tried the above code but the performance still the same.TonyTam 17:50, 8 April 2013 (EEST)
Mansewiz - Slow performance.
With my current project, I get 15-16 fps on the Lumia 920 with a gray effect. (18fps without any effects). It's still a far cry from 30fps, but pretty decently smooth. I made quite a few changes to get there, my code needs quite a lot of cleanup, but I'm planning to update the project with these changes during the week-end.Mansewiz 08:44, 9 April 2013 (EEST)
Small2 - Slow performance
Well , it wasn't works smoothly with complex effect. And I thought it was unnecessary to pass a PhotoCaptureDevice object to the d3d layer, just pass the camera data.
void ApplyEffect(const Platform::Array<int,1U>^ imageBuffer,Platform::WriteOnlyArray<uint8,1U>^outBuffer, int effectIndex);
ICameraCaptureDevice _camera; WindowsPhoneRuntimeComponent _cameraBuffer;
_camera.GetPreviewBufferArgb(_cameraData);
_cameraBuffer.ApplyEffect(_cameraData, _cameraFilteredData, EffectId);
I got an idea to implement the real-time effect with d3d, but I have little knowledge about the d3d. And I post it on the discussion board, but few guys replied. here was the url: http://www.developer.nokia.com/Community/Discussion/showthread.php?239753-troubles-on-showing-argb-data-in-a-surface-control
looking forward your ideas, many thanks for the wonderful wiki post!small2 12:53, 13 April 2013 (EEST)
Mansewiz - Slow performance
Hey there. I just pushed an update to the project. I now get 23fps with a Lumia 920, compared to the original 9 fps. Not bad! I did a *lot* of changes, the code is still a bit dirty, I will clean it up and make update the wiki article when I get a minute. I don't really know what in all the changes I did improved the speed that much, but most likely the change of the interface between the native and managed components helped a lot. Hopefully those changes work for you too!
-BertMansewiz 21:59, 13 April 2013 (EEST)
Intellsys - Slow performance
Nice article, I have traced a bit more accurately the FPS and processing time as follows:
In CameraStreamSource.cs
MediaStreamSample msSamp = new MediaStreamSample(
videoStreamDescription, dataSource.FrameStream, frameStreamOffset, dataSource.FrameBufferSize, currentTime, emptySampleDict);DateTime now = DateTime.Now; frameDuration = (now - startTime).Ticks; currentTime += frameDuration; startTime = now;And in MainPage.xaml.cs:
void m_timer_Tick(object sender, EventArgs e)
{ string str = "FPS: " + MyCameraMediaElement.RenderedFramesPerSecond + " Frame duration: " + source.frameDuration + " Processing FPS: " + 10000000.0/source.frameDuration; System.Diagnostics.Debug.WriteLine(str); this.FramerateInfo.Text = str; }In debug I get:
FPS: 20 Frame duration: 519468 Processing FPS: 19.2504639361809 FPS: 20 Frame duration: 434049 Processing FPS: 23.0388734912418 FPS: 21 Frame duration: 499328 Processing FPS: 20.0269161753397 FPS: 21 Frame duration: 423652 Processing FPS: 23.6042789836942 FPS: 21 Frame duration: 472787 Processing FPS: 21.1511737843892 FPS: 21 Frame duration: 504598 Processing FPS: 19.817755916591 FPS: 21 Frame duration: 509412 Processing FPS: 19.6304759212582 FPS: 21 Frame duration: 376692 Processing FPS: 26.5468871120172 FPS: 21 Frame duration: 459377 Processing FPS: 21.7686127080807 FPS: 21 Frame duration: 469194 Processing FPS: 21.313145521895 FPS: 21 Frame duration: 449063 Processing FPS: 22.2685903759606 FPS: 21 Frame duration: 474571 Processing FPS: 21.0716626173955 FPS: 21 Frame duration: 444133 Processing FPS: 22.5157779313854 FPS: 21 Frame duration: 419735 Processing FPS: 23.8245559698381 FPS: 21 Frame duration: 476437 Processing FPS: 20.9891339253668 FPS: 21 Frame duration: 474308 Processing FPS: 21.0833466861196 FPS: 21 Frame duration: 432776 Processing FPS: 23.1066417731113 FPS: 21 Frame duration: 499353 Processing FPS: 20.0259135321106 FPS: 21 Frame duration: 344602 Processing FPS: 29.0189842194764 FPS: 21 Frame duration: 438889 Processing FPS: 22.7848043582774 FPS: 21 Frame duration: 369723 Processing FPS: 27.0472759336043 FPS: 21 Frame duration: 390983 Processing FPS: 25.5765595946627 FPS: 21 Frame duration: 364431 Processing FPS: 27.440036659889 FPS: 21 Frame duration: 333032 Processing FPS: 30.027144538663
And in release I constantly get FPS 25-26 (from MediaElement RenderedFramesPerSecond ) and Processing FPS 20-37. Looks like the framerate reported by the MediaElement is not fully correlated with the processing time of the frames.(Btw, I have used a Samsung Ativ S.)
BR,
Lauintellsys 12:50, 18 April 2013 (EEST)
Mansewiz - Slow performance
Thanks Lau,
It is great to hear that performance of the latest version of the project is good on the Ativ S ! I guess the difference you're seeing between the fps from the MediaElement and processing counter comes from the ME doing the fps calculation on a longer time window: averaging over a longer time period (25-26 fps is somewhere in the middle of FPS20-37)Mansewiz 08:32, 19 April 2013 (EEST)
Arash k - Great Atcile
Hi Mansewiz
Thanks for the great article. It helped me alot.
I am quite new in Silverlight and Windows Phone development. I have tried to find a way to save the output of MediaStreamSource in your article to a file (for example mp4 format) in Place of playing it in a MediaElemtn and I couldn't find any. Is it possible to do that?
BR,
Arash.arash_k 12:01, 23 April 2013 (EEST)
Mansewiz - Saving mediaElement stream as mp4
Arash, you will find a good example how to record video here: http://www.developer.nokia.com/Community/Wiki/What%27s_new_in_Windows_Phone_8#Camera:_Video_Recording
Unfortunately, right now in WP8 it is not possible to encode as a video (mp4) any image frame streams other than the one coming directly from the camera. Say you want to modify the frames coming from camera (like doing a black and white effect) and save the resulting stream as a .mp4, the platform won't provide you the functionality you need. Your app could provide it's own video encoder, but building your own encoder is a *lot* of hard work.Mansewiz 12:54, 23 April 2013 (EEST)
Arash k -
Thank a lotarash_k 13:17, 23 April 2013 (EEST)
Besessener - Errors
Hi Mansewiz,
I tried your source code. I clone the git repository and opened the SLN file in Visual Studio 2012. But I can't compile it. It outputs the following errors:
Error 1 error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "pch.h"' to your source? c:\users\matze\desktop\nativefilterdemo\cameraeffectinterface\icameraeffect.cpp 7 Error 2 error C1192: #using failed on 'C:\Users\Matze\Desktop\NativeFilterDemo\Debug\CameraEffectInterface\CameraEffectInterface.winmd' c:\users\matze\desktop\nativefilterdemo\nativecomponent\pch.cpp 1 Error 3 Metadata file 'C:\Users\Matze\Desktop\NativeFilterDemo\Debug\CameraEffectInterface\CameraEffectInterface.winmd' could not be found C:\Users\Matze\Desktop\NativeFilterDemo\NativeFilterDemo\CSC Error 4 Metadata file 'C:\Users\Matze\Desktop\NativeFilterDemo\Debug\NativeComponent\NativeComponent.winmd' could not be found C:\Users\Matze\Desktop\NativeFilterDemo\NativeFilterDemo\CSC
I hope it's somthing that's very easy to fix and I also hope you tell me :)
Cheers,
Matthiasbesessener 17:20, 10 May 2013 (EEST)
Besessener - save image
Me again...
How would one save a single frame of this stream? When I try to store the stream in a BitmapImage: im.SetSource(_frameStream);
i get null exceeption although the stream is not null :(
cheersbesessener 22:05, 12 May 2013 (EEST)
Mansewiz - Errors
Oh! I didn't try to deploy the latest commit to the emulator, and it obviously didn't work. I've fix the problem (a simple configuration flag) to the tip. Thanks Matthias!Mansewiz 08:35, 13 May 2013 (EEST)
Mansewiz - save image
@Besessener, To save the image to a file, you should save to a png, see this entry in Stackoverflow: http://stackoverflow.com/questions/7378946/saving-bitmap-as-png-on-wp7
If what you're looking for is create a bitmap from the stream, you can use the WriteableBitmap:
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => { WriteableBitmap wb = new WriteableBitmap(dataSource.FrameWidth, dataSource.FrameHeight); byte[] buf = dataSource.FrameStream.GetBuffer(); //When creating FrameStream make sure the buffer is publicly visible, it is not by default. Buffer.BlockCopy(buf, 0, wb.Pixels, 0, buf.Length); });This isn't super efficient, since it introduces a thread switch to UI and an full copy of the array, but if you're not doing it several times a seconds, it should be ok.Mansewiz 09:50, 13 May 2013 (EEST)