Я пытаюсь реализовать функцию скриншота в приложении WinRT, которое показывает видео через MediaElement. У меня есть следующий код, он сохраняет скриншот размером с MediaElement, но изображение пустое (полностью черное). Пробовал с различными типами медиа файлов. Если я нажимаю клавишу Win + Vol Down на Surface RT, снимок экрана содержит содержимое фрейма «Медиа», но если я использую следующий код, все вокруг становится черным 🙁
private async Task SaveCurrentFrame()
{
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(Player);
var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
MultimediaItem currentItem = (MultimediaItem)this.DefaultViewModel["Group"];
StorageFolder currentFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
var saveFile = await currentFolder.CreateFileAsync(currentItem.UniqueId + ".png", CreationCollisionOption.ReplaceExisting);
if (saveFile == null)
return;
// Encode the image to the selected file on disk
using (var fileStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, fileStream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)renderTargetBitmap.PixelWidth,
(uint)renderTargetBitmap.PixelHeight,
DisplayInformation.GetForCurrentView().LogicalDpi,
DisplayInformation.GetForCurrentView().LogicalDpi,
pixelBuffer.ToArray());
await encoder.FlushAsync();
}
}
Здесь MultimediaItem — это мой класс View Model, который помимо прочего имеет свойство UniqueId, представляющее собой строку.
«Player» — это название медиа-элемента.
Что-то не так с кодом или этот подход не так, и я должен попасть в окопы с C ++?
Постскриптум Я заинтересован только в WinRT API.
Обновление 1 Похоже, что RenderTargetBitmap не поддерживает это, документация MSDN разъясняет это http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.imaging.rendertargetbitmap .
Я буду признателен за любые советы о том, как сделать это с помощью DirectX C ++. Для меня это большая задача, поэтому я так или иначе взломаю ее и сообщу о решении.
да, это возможно — немного сложно, но работает хорошо.
Вы не используете mediaElement
, но StorageFile
сам.
Вам нужно создать writableBitmap
с помощью Windows.Media.Editing Пространство имен.
Работает в UWP (Windows 10)
Это полный пример с выбором файла, получением разрешения видео и сохранением изображения в библиотеке изображений
TimeSpan timeOfFrame = new TimeSpan(0, 0, 1);//one sec
//pick mp4 file
var picker = new Windows.Storage.Pickers.FileOpenPicker();
picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
picker.FileTypeFilter.Add(".mp4");
StorageFile pickedFile = await picker.PickSingleFileAsync();
if (pickedFile == null)
{
return;
}
/////Get video resolution
List<string> encodingPropertiesToRetrieve = new List<string>();
encodingPropertiesToRetrieve.Add("System.Video.FrameHeight");
encodingPropertiesToRetrieve.Add("System.Video.FrameWidth");
IDictionary<string, object> encodingProperties = await pickedFile.Properties.RetrievePropertiesAsync(encodingPropertiesToRetrieve);
uint frameHeight = (uint)encodingProperties["System.Video.FrameHeight"];
uint frameWidth = (uint)encodingProperties["System.Video.FrameWidth"];
/////Use Windows.Media.Editing to get ImageStream
var clip = await MediaClip.CreateFromFileAsync(pickedFile);
var composition = new MediaComposition();
composition.Clips.Add(clip);
var imageStream = await composition.GetThumbnailAsync(timeOfFrame, (int)frameWidth, (int)frameHeight, VideoFramePrecision.NearestFrame);
/////generate bitmap
var writableBitmap = new WriteableBitmap((int)frameWidth, (int)frameHeight);
writableBitmap.SetSource(imageStream);//generate some random name for file in PicturesLibrary
var saveAsTarget = await KnownFolders.PicturesLibrary.CreateFileAsync("IMG" + Guid.NewGuid().ToString().Substring(0, 4) + ".jpg");//get stream from bitmap
Stream stream = writableBitmap.PixelBuffer.AsStream();
byte[] pixels = new byte[(uint)stream.Length];
await stream.ReadAsync(pixels, 0, pixels.Length);
using (var writeStream = await saveAsTarget.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, writeStream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)writableBitmap.PixelWidth,
(uint)writableBitmap.PixelHeight,
96,
96,
pixels);
await encoder.FlushAsync();
using (var outputStream = writeStream.GetOutputStreamAt(0))
{
await outputStream.FlushAsync();
}
}
Да … Я провел много часов этим
Хорошо, мне удалось сделать снимок из MediaElement при нажатии кнопки для работы.
Я передаю объект MediaStreamSource в MediaElement, используя метод SetMediaStreamSource. MediaStreamSource имеет событие SampleRequested, которое запускается при каждом рисовании нового кадра. Затем, используя логическое значение, я контролирую, когда создавать растровые изображения.
private async void MediaStream_SampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
if (!takeSnapshot)
{
return;
}
takeSnapshot = false;
Task.Run(() => DecodeAndSaveVideoFrame(args.Request.Sample));
}
После этого остается декодировать сжатое изображение и преобразовать его в WriteableBitmap. Изображение есть (или, по крайней мере, было в моем случае) в YUV fromat. Вы можете получить байтовый массив, используя
byte[] yvuArray = sample.Buffer.ToArray();
а затем получить данные из этого массива и преобразовать его в RGB. К сожалению, я не могу опубликовать весь код, но я дам вам еще несколько советов:
YUV в RGB вики Здесь у вас есть вики, описывающая, как работает конвертация YUV в RGB.
Здесь я нашел проект Python какое решение я адаптировал (и работает отлично). Чтобы быть более точным, вы должны проанализировать, как работает метод NV12Converter.
Последнее, что нужно сделать, это изменить логическое значение takeSnapshot на true после нажатия кнопки или выполнения других действий :).