如何从WinRT(8.1)中的MediaElement捕获当前帧?

时间:2013-11-09 01:46:43

标签: c++ windows-runtime winrt-xaml rendertargetbitmap

我正在尝试在通过MediaElement显示视频的WinRT应用中实现屏幕截图功能。我有以下代码,它保存了一个截图,它是MediaElement的大小,但图像是空的(全黑)。试过各种类型的媒体文件。如果我在Surface RT上执行Win Key + Vol Down,屏幕截图包含Media框架内容,但是如果我使用以下代码,那么它的黑度就是:(

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属性。

'播放器'是媒体元素的名称。

代码有什么问题或者这种方法是错误的,我要用C ++进入战壕?

P.S。我只对WinRT API感兴趣。

更新1 看起来RenderTargetBitmap不支持此功能,MSDN文档澄清了它http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.imaging.rendertargetbitmap。 我会很感激有关如何使用DirectX C ++的指示。这对我来说是一项重大任务,因此我将以这种方式或其他方式进行破解,并使用解决方案进行报告。

2 个答案:

答案 0 :(得分:2)

,这有可能 - 有点棘手,但运作良好。

您不使用mediaElement,而是StorageFile本身。 您需要在Windows.Media.Editing命名空间的帮助下创建writableBitmap

适用于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();
            }
        }

是的......我花了很多时间来这个

答案 1 :(得分:2)

好的,我已经设法在按钮按下时从MediaElement制作快照。

我使用SetMediaStreamSource方法将MediaStreamSource对象传递给MediaElement。 MediaStreamSource具有事件SampleRequested,每次绘制新帧时都会基本触发该事件。然后使用布尔I控制何时创建位图

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 to RGB wiki这里有wiki描述了YUV到RGB转换的工作原理。

Here I found python project我已经采用了哪种解决方案(并且工作得很好)。更准确地说,你必须分析NV12Converter方法的工作原理。

最后一件事是在按下按钮或进行其他活动后将takeSnapshot boolean更改为true:)。