Windows Phone 8 has introduced new APIs to interact with the native camera and to take pictures and record videos. These APIs are much more powerful than the ones that are available in Windows Phone 7: you have full control over all the camera settings, like exposure, focus, flash and so on.
There’s a good example in the MSDN documentation about how to use them: unlucky the article has some minor issues, so you can face some problems. Let’s see, in details, how to use the new APIs and how to apply a workaround to fix these issues.
Usually the first goal you want to achieve when you develop a camera application is to display the camera’s stream in your application: for this purpose we’re going to use a VideoBrush, which is one of the available brush in XAML that can be used to customize a control.
We’re going to insert a VideoBrush inside a Canvas, to display the camera’s stream inside it:
<Canvas Height="400" Width="400"> <Canvas.Background> <VideoBrush x:Name="video" Stretch="UniformToFill"> <VideoBrush.RelativeTransform> <CompositeTransform x:Name="previewTransform" CenterX=".5" CenterY=".5" /> </VideoBrush.RelativeTransform> </VideoBrush> </Canvas.Background> </Canvas>
You can notice that we apply also a CompositeTransform to the brush: as we’ll see soon, this is needed to rotate the stream according to the phone’s rotation.
Now we need to get access to the camera in our code: we’re going to use the new class PhotoCaptureDevice, that has replaced the PhotoCamera class that was available in Windows Phone 7. The old class is still there but, if you don’t need to keep the compatibility with both platforms, I suggest you to use the new one, since it’s much more powerful.
We’re going to initialize the camera in the OnNavigatedTo event:
public partial class MainPage : PhoneApplicationPage { public MainPage() { InitializeComponent(); } private PhotoCaptureDevice camera; protected override async void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); Size resolution = PhotoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back).First(); camera = await PhotoCaptureDevice.OpenAsync(CameraSensorLocation.Back, resolution); video.SetSource(camera); previewTransform.Rotation = camera.SensorRotationInDegrees; } }
The first step is to define which is the resolution we’re going to use to take our photos: for this reason we use the GetAvailableCaptureResolutions() method of the PhotoCaptureDevice class, passing as parameter which camera we want to use: CameraSensorLocation.Back for the rear camera and CameraSensorLocation.Front for the front camera. The method returns a list of all the supported resolutions by the camera, starting from the highest to the lowest. In our example we take the picture using the maximum available resolution, so we take just the first of the list and we pass it, as parameter, to the OpenAsync method of the PhotoCaptureDevice class, together (again) with the camera we want to use. We use the await keyword (and, as a consequence, we mark the OnNavigatedTo method with the async keyword) since it’s asynchronous.
The last step is to display the camera’s stream on the screen, by passing the PhotoCaptureDevice object to the SetSource method of the VideoBrush. We also set the rotation of the transformation applied to the brush according to the rotation of the camera, using the SensorRotationInDegrees property.
Important! Notice that, to do this operation, we need to add in our class the namespace Microsoft.Device: this is required to set the PhotoCaptureDevice object as a source for the VideoBrush, since it adds an override of the SetSource method that accepts as parameter an ICameraCaptureDevice object, that is the base interface from which every new camera API inherits from.
If you just launch the application you’ll see the image recorded by the camera on the screen: if you test the application on the emulator, it will be generated a fake stream, since the emulator isn’t able to use the computer’s webcam.
Take the picture
The new Windows Phone 8 APIs have introduced the concept of “sequence of frames”, which is a set of pictures taken at the same time. In the current version the frames are limited to 1, so you can capture sequences composed by just one picture.
Here is a sample code that takes the picture and saves it into the camera roll of the phone:
private async void OnTakePhotoClicked(object sender, RoutedEventArgs e) { CameraCaptureSequence cameraCaptureSequence = camera.CreateCaptureSequence(1); MemoryStream stream = new MemoryStream(); cameraCaptureSequence.Frames[0].CaptureStream = stream.AsOutputStream(); await camera.PrepareCaptureSequenceAsync(cameraCaptureSequence); await cameraCaptureSequence.StartCaptureAsync(); stream.Seek(0, SeekOrigin.Begin); MediaLibrary library = new MediaLibrary(); library.SavePictureToCameraRoll("picture", stream); }
First we create a new CameraCaptureSequence object, by using the CreateCaptureSequence method of the PhotoCaptureDevice object. As parameter we need to pass the number of frames to capture: as I’ve just explained, due to a limit in the current APIs, we have to pass 1 as value; other values are not supported.
Then we create a new MemoryStream object, that will hold in memory the photo captured by the camera: we connect the output of the stream to output of the first frame of the sequence, that is the photo that will be captured. We do this by using the CaptureStream property of the first stream, that is available in the Frames collection of the sequence.
In the end, we take the picture: first we prepare the sequence (by calling the PrepareCaptureSequenceAsync method of the PhotoCaptureDevice object) an then we call the StartCatureAsync method on the sequence. Both methods are asynchronous, so we use the await keyword.
Now that we have the picture, we save it in the phone media library: and here is where need to apply a workaround, that is not mentioned in the MSDN article. We need to set the stream’s position of the MemoryStream object to the beginning, otherwise you’ll get a cryptic InvalidOperationException error with the An unexpected error has occured message, that isn’t helpful. The credit to this workaround goes to Marco Leoncini, a dear friend of mine and a member of the ASPItalia crew, that has helped me to understand why the sample provided in the MSDN article didn’t work.
We have two options to save the picture in the library: in the sample we use the SavePictureToCameraRoll method of the MediaLibrary object to save the picture in the camera roll. We have also another method, SavePicture, that can be used to save the image in the Saved pictues folder of the media library. It’s up to you choosing which is the best for your needs: if you’re developing a camera-replacement application (for example, a Lens app) probably you would prefer to save it in the camera roll, to be consistent with the experience provided by the native application.
Happy coding!
P.S. I would like to thank you also Lance Mc Carthy, a Nokia ambassador that helped me to track the issue and that it’s been very kind to quickly reply to my questions on Twitter.
Excellent write-up. I definitely love this website.
Keep it up!
when I add the line
Size resolution = PhotoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back).First();
into my project an error occur: cannot implicitly convert type windows.foundation.size to system.windows.size. Why?I already added Windows.Phone.Media.Capture; Microsoft.Devices; libraries as you suggest..
Hi, please check that you’re using the proper Size property: to work with the camera, you should use just the one defined in the Windows.Foundation namespace.
Yes, that was the problem. I seen your sample code then I added:
using Size = Windows.Foundation.Size;
Now it works!
thanks it also worked for me. Gave me a headache 3 days 🙂
Great solution, saved a lot of time. Thanks.
Excellent sample and explaination. Thank you so much.
i had problems saving to the photo library. You just pointed out the solution. I had to set the stream object to the beginning. Thanks man