Reads audio/video data from files, cameras, microphone, and generic media sources
MediaReader
reads audio/video data from files (StorageFile
, IRandomAccessStream
, path), cameras and microphone (MediaCapture
), and generic sources (IMediaSource
). Universal Store apps targeting either Windows or Windows Phone are supported. A NuGet package is available here.
MediaReader
allows for instance reading video frames from an MP4 file and saving them as a series of JPEG images:
using (var reader = await MediaReader.CreateFromPathAsync("ms-appx:///car.mp4"))
{
while (true)
{
using (var result = await reader.VideoStream.ReadAsync())
{
if (result.EndOfStream || result.Error)
{
break;
}
var sample = (MediaSample2D)result.Sample;
if (sample == null)
{
continue;
}
var file = await folder.CreateFileAsync(((int)sample.Timestamp.TotalMilliseconds).ToString("D6") + ".jpg");
await sample.SaveToFileAsync(file, ImageCompression.Jpeg);
}
}
}
Three static methods are available to create MediaReader
from StorageFile
, IRandomAccessStream
, and path: CreateFromFileAsync()
, CreateFromStreamAsync()
, and CreateFromPathAsync()
respectively. CreateFromPathAsync
in particular is useful to read files in the app package using the ms-appx
scheme:
var reader = await MediaReader.CreateFromPathAsync("ms-appx:///car.mp4");
Data from cameras and microphones is read by creating MediaCapture
and passing it to MediaReader.CreateFromMediaCaptureAsync()
:
var capture = new MediaCapture();
await capture.InitializeAsync();
var reader = await MediaReader.CreateFromMediaCaptureAsync(capture);
A SelectVideoDeviceAsync()
extension method is provided on MediaCaptureInitializationSettings
to simplify selecting the back and front cameras:
using MediaCaptureReaderExtensions;
var settings = new MediaCaptureInitializationSettings
{
// Video-only capture
StreamingCaptureMode = StreamingCaptureMode.Video
};
if (!await settings.SelectVideoDeviceAsync(VideoDeviceSelection.BackOrFirst))
{
// No camera
return;
}
var capture = new MediaCapture();
await _capture.InitializeAsync(settings);
SelectVideoDeviceAsync()
returns false
if no camera is present.
MediaReader
can also read data from IMediaSource
using CreateFromMediaSourceAsync()
. This can be used in conjunction with the HttpMjpegCaptureSource
class in the same package, a media source streaming MJPEG videos from IP cameras:
var source = await HttpMjpegCaptureSource.CreateFromUriAsync("http://216.123.238.208/axis-cgi/mjpg/video.cgi?camera&resolution=640x480");
var reader = await MediaReader.CreateFromMediaSourceAsync(source.Source);
The AudioInitialization
and VideoInitialization
enumerations control data processing in MediaReader
. They can be used for instance to read the video data as Bgra8 while ignoring the audio data:
var reader = await MediaReader.CreateFromFileAsync(file, AudioInitialization.Deselected, VideoInitialization.Bgra8);
Audio streams can be initialized as Pcm
, PassThrough
, or Deselected
, while video streams can be initialized as Nv12
, Bgra8
, PassThrough
, or Deselected
. The defaults are Nv12
and Pcm
. In PassThrough
mode the data is provided in the format it is stored in the file, which is typically compressed (say H.264 and AAC).
The AudioStream
and VideoStream
properties on MediaReader
provide ReadAsync()
methods to read samples from the first audio and video streams in the file. The properties are null if the file has no audio or no video data. An AllStreams
property also gives access to all the available streams.
using (var result = await _mediaReader.VideoStream.ReadAsync())
The ReadAsync()
methods return MediaReaderReadResult
objects which provide information about the stream state (EndOfStream
, Error
, etc. properties) and give out the data just read (Sample
property). The Sample
property is of type IMediaSample
. As-is it gives access to the sample time and duration. It can also be cast to MediaSample2D
when reading uncompressed video and MediaSample1D
when reading compressed video or either compressed or uncompressed audio. The LockBuffer()
methods on those two classes give access to the sample data.
using (var reader = await MediaReader.CreateFromPathAsync("ms-appx:///Recording.m4a"))
{
while (true)
{
using (var result = await reader.AudioStream.ReadAsync())
{
if (result.EndOfStream || result.Error)
{
break;
}
var sample = (MediaSample1D)result.Sample;
if (sample == null)
{
continue;
}
// Use audio data here
}
}
}
ImageProcessor
provides methods to convert the format/width/height of 2D samples and rotate them:
var processor = new ImageProcessor();
var sample1 = new MediaSample2D(MediaSample2DFormat.Nv12, 320, 240);
var sample2 = processor.Rotate(sample1, BitmapRotation.Clockwise90Degrees);
var sample3 = processor.Convert(sample2, MediaSample2DFormat.Bgra8, 480, 640);
ImageEncoder
encodes 2D samples to JPEG:
var file = await KnownFolders.PicturesLibrary.CreateFileAsync("Image.jpg");
await ImageEncoder.SaveToFileAsync(sample, file, ImageCompression.Jpeg);
To simplify that code a bit, extension methods on MediaSample2D
are also provided:
using MediaCaptureReaderExtensions;
await sample.SaveToFileAsync(file, ImageCompression.Jpeg);
ImageDecoder
decodes 2D samples from JPEG files:
var sample = await ImageDecoder.LoadFromPathAsync("ms-appx:///Car.jpg", MediaSample2DFormat.Nv12);
ImagePresenter
displays 2D samples in XAML using either SurfaceImageSource
var image = new SurfaceImageSource((int)previewProps.Width, (int)previewProps.Height);
var imagePresenter = ImagePresenter.CreateFromSurfaceImageSource(image, graphicsDevice);
var imagePresenter.Present(sample);
or SwapChainPanel
var swapChainPresenter = ImagePresenter.CreateFromSwapChainPanel(
SwapChainPreview,
graphicsDevice,
(int)previewProps.Width,
(int)previewProps.Height
);
swapChainPresenter.Present(sample);
When debugging C++ code, the Image Watch debugger extension can help visualize the current content of uncompressed 2D samples:
To enable this, install Image Watch and copy this Natvis file to the Visual Studio Visualizers folder, which is typically C:\Users\%USERNAME%\Documents\Visual Studio 2013\Visualizers.
The content of MediaSample2D and MediaBuffer2D is displayed by clicking on the associated magnifying glasses in debugger tooltips and Autos/Locals/Watch windows.
var barcodeReader = new BarcodeReader
{
PossibleFormats = new BarcodeFormat[] { BarcodeFormat.QR_CODE }
};
using (var mediaReader = await MediaReader.CreateFromPathAsync("ms-appx:///QR_12345678.mp4", AudioInitialization.Deselected, VideoInitialization.Bgra8))
using (var mediaResult = await mediaReader.VideoStream.ReadAsync())
{
var sample = (MediaSample2D)mediaResult.Sample;
using (var buffer = sample.LockBuffer(BufferAccessMode.Read))
{
var barcodeResult = barcodeReader.Decode(
buffer.Planes[0].Buffer.ToArray(),
buffer.Width,
buffer.Height,
BitmapFormat.BGR32
);
...
}
}
using (var mediaReader = await MediaReader.CreateFromPathAsync("ms-appx:///car.mp4", AudioInitialization.Deselected, VideoInitialization.Nv12))
using (var mediaResult = await mediaReader.VideoStream.ReadAsync())
{
var inputSample = (MediaSample2D)mediaResult.Sample;
using (var outputSample = new MediaSample2D(MediaSample2DFormat.Nv12, inputSample.Width, inputSample.Height))
using (var inputBuffer = inputSample.LockBuffer(BufferAccessMode.Read))
using (var outputBuffer = outputSample.LockBuffer(BufferAccessMode.Write))
{
// Wrap MediaBuffer2D in Bitmap
var inputBitmap = new Bitmap(
new Size(inputSample.Width, inputSample.Height),
ColorMode.Yuv420Sp,
new uint[] { inputBuffer.Planes[0].Pitch, inputBuffer.Planes[1].Pitch },
new IBuffer[] { inputBuffer.Planes[0].Buffer, inputBuffer.Planes[1].Buffer }
);
var outputBitmap = new Bitmap(
new Size(inputSample.Width, inputSample.Height),
ColorMode.Yuv420Sp,
new uint[] { outputBuffer.Planes[0].Pitch, outputBuffer.Planes[1].Pitch },
new IBuffer[] { outputBuffer.Planes[0].Buffer, outputBuffer.Planes[1].Buffer }
);
// Apply effect
var effect = new FilterEffect();
effect.Filters = new IFilter[] { new WatercolorFilter() };
effect.Source = new BitmapImageSource(inputBitmap);
var renderer = new BitmapRenderer(effect, outputBitmap);
await renderer.RenderAsync();
}
}
For efficiency, a GetData()
extension method is added on IBuffer
. GetData()
returns an 'unsafe' byte*
pointing to the IBuffer
data. This requires methods calling GetData()
to be marked using the unsafe
keyword and to check the 'Allow unsafe code' checkbox in the project build properties.
The following code snippet reads RGB values and doubles them:
using MediaCaptureReaderExtensions;
private unsafe void ProcessSample(MediaSample2D sample)
{
using (var buffer = sample.LockBuffer(BufferAccessMode.ReadWrite))
{
int width = buffer.Width;
int height = buffer.Height;
int pitch = (int)buffer.Planes[0].Pitch;
byte* data = buffer.Planes[0].Buffer.GetData();
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int pos = i * pitch + 4 * j;
data[pos + 0] = (byte)Math.Min(255, 2 * data[pos + 0]); // B
data[pos + 1] = (byte)Math.Min(255, 2 * data[pos + 1]); // G
data[pos + 2] = (byte)Math.Min(255, 2 * data[pos + 2]); // R
}
}
}
}
The HttpMjpegCaptureSource
class streams MJPEG video from IP cameras. It can for instance be passed to XAML's <MediaElement>
to preview the video stream:
<MediaElement Name="VideoPreview" AutoPlay="True" RealTimePlayback="True" />
var source = await HttpMjpegCaptureSource.CreateFromUriAsync("http://216.123.238.208/axis-cgi/mjpg/video.cgi?camera&resolution=640x480");
VideoPreview.SetMediaStreamSource(source.Source);
CaptureReader
reads audio/video samples from MediaCapture
. The following code snippet reads a video sample in Bgra8 format from the default camera:
# Create MediaCapture
var capture = new MediaCapture();
await capture.InitializeAsync();
# Create a Bgra8 video format matching the camera resolution and framerate
var previewProps = (VideoEncodingProperties)capture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview);
var readerProps = VideoEncodingProperties.CreateUncompressed(MediaEncodingSubtypes.Bgra8, previewProps.Width, previewProps.Height);
readerProps.FrameRate.Numerator = previewProps.FrameRate.Numerator;
readerProps.FrameRate.Denominator = previewProps.FrameRate.Denominator;
# Create CaptureReader and get a video sample
var captureReader = await CaptureReader.CreateAsync(
capture,
new MediaEncodingProfile
{
Video = readerProps
});
MediaSample sample = await captureReader.GetVideoSampleAsync();