Quickstart: capturing a variable photo sequence (XAML)
[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]
This topic shows you how to capture a variable photo sequence, which allows you to capture multiple frames of images in rapid succession and configure each frame to use different focus, ISO, exposure, and exposure compensation settings. This feature enables scenarios like creating High Dynamic Range (HDR) images.
For an example of a full-featured camera app that implements variable photo sequence capture, recommended as a starting place for building your own camera app, see Advanced Camera Sample. This sample is currently only available for Windows Phone.
Roadmap: How does this topic relate to others? See:
- Roadmap for Windows Runtime apps using C# or Visual Basic
- Roadmap for Windows Runtime apps using C++
Prerequisites
This topic assumes that you know how to create a basic Windows Runtime app using C++, C#, or Visual Basic. For help creating your first app, see Create your first Windows Store app using C# or Visual Basic.
Initialize the MediaCapture object
Variable photo sequences are captured using the MediaCapture object. This example method creates a new instance of the MediaCapture object. Then it hooks up handlers for the Failed and RecordLimitationExceeded events. Next, InitializeAsync is called to initialize the object, passing in a MediaCaptureInitializationSettings object. The GetCameraDeviceInfoAsync helper method is used to get a DeviceInformation object that contains the ID of the device's rear facing camera.
public async Task InitializeCaptureAsync()
{
if (_mediaCapture != null)
{
return;
}
try
{
_deviceInformation = await GetCameraDeviceInfoAsync(Panel.Back);
_mediaCapture = new MediaCapture();
// Set the MediaCapture to a variable in App.xaml.cs to handle suspension.
(App.Current as App).MediaCapture = _mediaCapture;
_mediaCapture.Failed += MediaCaptureFailed;
_mediaCapture.RecordLimitationExceeded += RecordLimitationExceeded;
await _mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
{
StreamingCaptureMode = StreamingCaptureMode.Video,
PhotoCaptureSource = PhotoCaptureSource.Auto,
AudioDeviceId = string.Empty,
VideoDeviceId = _deviceInformation.Id
});
}
catch (Exception e)
{
NotifyUser("Exception in InitializeCaptureAsync: " + e.Message, NotifyType.ErrorMessage);
}
NotifyUser("Media capture initialized.", NotifyType.StatusMessage);
}
private static async Task<DeviceInformation> GetCameraDeviceInfoAsync(Windows.Devices.Enumeration.Panel desiredPanel)
{
DeviceInformation device = (await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture))
.FirstOrDefault(d => d.EnclosureLocation != null && d.EnclosureLocation.Panel == desiredPanel);
if (device == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "No suitable devices found for the camera of type {0}.", desiredPanel));
}
return device;
}
Prepare the variable photo sequence
Now you need to prepare the variable photo sequence. Get an instance of the VariablePhotoSequenceController class from the MediaCapture object. Check the Supported property to make sure the device your app is running on supports variable photo sequences.
Get a FrameCapabilities object to determine which settings you can adjust per frame on the device. This example checks to see if ExposureCompensation is supported. If the settings you need are supported, create a FrameController object for each frame in your variable photo sequence.
Warning The maximum number of frames supported for variable photo sequences is 18.
For each FrameController set the value of each setting your are modifying and then add each frame to the DesiredFrameControllers list of the VariablePhotoSequenceController. This example also initializes an array of CapturedFrame objects and an array of CapturedFrameControlValues that will be used to store the captured frames and the control values for each frame.
The last lines of this example create an image encoding to be used and then call PrepareVariablePhotoSequenceCaptureAsync to prepare the capture device. A handler is hooked up for PhotoCaptured, which will be called once for each captured frame, and another is hooked up for the Stopped event, which is called when all of the frames have been captured.
public async Task PrepareVariablePhotoSequence()
{
try
{
var varPhotoSeqController = _mediaCapture.VideoDeviceController.VariablePhotoSequenceController;
if (!varPhotoSeqController.Supported)
{
NotifyUser("Variable Photo Sequence is not supported", NotifyType.StatusMessage);
return;
}
var frameCapabilities = varPhotoSeqController.FrameCapabilities;
if (frameCapabilities.ExposureCompensation.Supported)
{
var frame0 = new FrameController();
var frame1 = new FrameController();
var frame2 = new FrameController();
frame0.ExposureCompensationControl.Value = -1.0f;
frame1.ExposureCompensationControl.Value = 0.0f;
frame2.ExposureCompensationControl.Value = 1.0f;
varPhotoSeqController.DesiredFrameControllers.Clear();
varPhotoSeqController.DesiredFrameControllers.Add(frame0);
varPhotoSeqController.DesiredFrameControllers.Add(frame1);
varPhotoSeqController.DesiredFrameControllers.Add(frame2);
_images = new CapturedFrame[3];
_frameControlValues = new CapturedFrameControlValues[3];
}
else
{
NotifyUser("EVCompenstaion is not supported in FrameController", NotifyType.StatusMessage);
return;
}
var format = ImageEncodingProperties.CreateJpeg();
format.Width = 640;
format.Height = 480;
_photoCapture = await _mediaCapture.PrepareVariablePhotoSequenceCaptureAsync(format);
_photoCapture.PhotoCaptured += OnPhotoCaptured;
_photoCapture.Stopped += OnStopped;
}
catch(Exception ex)
{
NotifyUser("Exception in PrepareVariablePhotoSequence: " + ex.Message, NotifyType.ErrorMessage);
}
}
Start the sequence capture
When you are ready to start capturing the variable photo sequence, call StartAsync. This example also resets a counter that will be incremented with each captured frame.
async void OnShutterButtonPressed(object sender, RoutedEventArgs e)
{
_photoIndex = 0;
await _photoCapture.StartAsync();
// Set a tracking variable for recording state in App.xaml.cs
(App.Current as App).IsRecording = true;
}
Handling the PhotoCaptured event
The PhotoCaptured event is raised for each captured frame in the photo sequence. In this example, the captured frame and the settings used to capture each frame are stored in their respective arrays and the frame counter is incremented.
void OnPhotoCaptured(VariablePhotoSequenceCapture s, VariablePhotoCapturedEventArgs e)
{
_images[_photoIndex] = e.Frame;
_frameControlValues[_photoIndex] = e.CapturedFrameControlValues;
_photoIndex++;
}
Warning
Focus information in the CapturedFrameControlValues property is only accurate if the preview is currently running.
Handling the Stopped event
The Stopped event is raised when all of the frames in the sequence have been captured. This is where you can save, upload, or process the captured frames. This example simulates creating an HDR image from the captured frames and then displays it to the user.
async void OnStopped(object s, object e)
{
// Set a tracking variable for recording state in App.xaml.cs
(App.Current as App).IsRecording = false;
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
var HDRImage = await DoHDRAsync(_images, _frameControlValues, 3);
_HDRimage.Source = HDRImage;
});
}
Note If a variable photo sequence capture has been started and is still running, you must stop the variable photo sequence capture by calling StopAsync before your app attempts to record video with the MediaCapture object.
Cleaning up MediaCapture resources properly
Warning It is extremely important that you properly shut down and dispose of the MediaCapture object and related objects when your app is suspended. Failure to do so could interfere with other apps accessing the device's camera which will result in a negative user experience for your app.
You should cleanup media capture resources when your app is suspended. In order to do this, your App class will need to access your MediaCapture object and its current state. A good way to do this is to declare some public properties in your app.xaml.cs file to store the MediaCapture object, the CaptureElement you are using for your preview, and booleans that indicate whether the app is currently recording or previewing video.
public MediaCapture MediaCapture { get; set; }
public CaptureElement PreviewElement { get; set; }
public bool IsRecording { get; set; }
public bool IsPreviewing { get; set; }
Create a function in App.xaml.cs that stops recording or previewing if they are in progress. If you are using a MediaElement, set its source to null. And finally, call Dispose on your MediaCapture object.
public async Task CleanupCaptureResources()
{
if (IsRecording && MediaCapture != null)
{
await MediaCapture.StopRecordAsync();
IsRecording = false;
}
if (IsPreviewing && MediaCapture != null)
{
await MediaCapture.StopPreviewAsync();
IsPreviewing = false;
}
if (MediaCapture != null)
{
if (PreviewElement != null)
{
PreviewElement.Source = null;
}
MediaCapture.Dispose();
}
}
Finally, add the following code to your OnSuspending event handler. It is very important that you get a deferral before calling your cleanup method. This ensures that the system lets the method complete before suspending your app.
private async void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//cleanup camera resources
await CleanupCaptureResources();
deferral.Complete();
}
Summary and next steps
This topic has given you an overview of capturing a variable photo sequence.