使用 MediaCapture 捕获基本的照片、视频和音频

本文介绍使用 MediaCapture 类捕获照片和视频的最简单方法。 MediaCapture 类公布了一组强大的 API,可提供捕获管道的低级别控制和启用高级捕获方案,但本文旨在帮助你将基本的媒体捕获快速且轻松地添加到应用。 若要了解有关 MediaCapture 提供的功能的详细信息,请参阅相机

如果你仅希望捕获照片或视频,不想添加任何其他媒体捕获功能,或者如果你不想创建自己的相机 UI,则可能希望使用 CameraCaptureUI 类,它允许你仅启动 Windows 内置相机应用,并且接收捕获的照片或视频文件。 有关详细信息,请参阅使用 Windows 内置相机 UI 捕获照片和视频

本文中的代码源自 Camera starter kit 示例。 你可以下载该示例以查看上下文中使用的代码,或将该示例用作你自己的应用的起始点。

向应用清单中添加功能声明

为了让你的应用可以访问设备的相机,必须声明你的应用要使用 webcammicrophone 设备功能。 如果你想要将已捕获的照片和视频保存到用户的图片库或视频库,还必须声明 picturesLibraryvideosLibrary 功能。

将功能添加到应用部件清单

  1. 在 Microsoft Visual Studio 的解决方案资源管理器中,通过双击 package.appxmanifest 项,打开应用程序清单的设计器。
  2. 选择功能选项卡。
  3. 选中摄像头框和麦克风框。
  4. 若要访问图片库和视频库,请选中图片库框和视频库框。

初始化 MediaCapture 对象

本文介绍的所有捕获方法都需要通过依次调用构造函数和 InitializeAsync,首先初始化 MediaCapture 对象。 由于在应用中可从多个位置访问 MediaCapture 对象,所以请声明某个类变量以保留该对象。 如果捕获操作失败,请为待通知的 MediaCapture 对象的 Failed 事件实现处理程序。

MediaCapture mediaCapture;
bool isPreviewing;
mediaCapture = new MediaCapture();
await mediaCapture.InitializeAsync();
mediaCapture.Failed += MediaCapture_Failed;

设置相机预览

使用 MediaCapture 无需显示相机预览即可捕获照片、视频和音频,但通常需要显示预览流,以便用户可以看到捕获的内容。 此外,某些 MediaCapture 功能要求先运行预览流才能进行启用,其中包括自动对焦、自动曝光和自动白平衡。 若要了解如何设置相机预览,请参阅显示相机预览

将照片捕获到 SoftwareBitmap

Windows 10 引入了 SoftwareBitmap 类,可跨多项功能提供图像的通用表示形式。 如果你想捕获照片并立即在应用中使用捕获的图像(例如在 XAML 中显示它,而非捕获到某个文件),则应捕获到 SoftwareBitmap。 你仍可选择在以后将图像保存到磁盘。

初始化 MediaCapture 对象后,可使用 LowLagPhotoCapture 类将照片捕获到 SoftwareBitmap。 通过调用 PrepareLowLagPhotoCaptureAsync(该函数在指定所需图像格式的 ImageEncodingProperties 对象中传递),获取此类的实例。 CreateUncompressed 使用指定的像素格式创建未压缩的编码。 通过调用返回 CapturedPhoto 对象的 CaptureAsync 捕获照片。 通过依次访问 Frame 属性和 SoftwareBitmap 属性,获取 SoftwareBitmap

如果需要,可重复调用 CaptureAsync 来捕获多张照片。 在完成捕获后,调用 FinishAsync 以关闭 LowLagPhotoCapture 会话,并释放关联的资源。 调用 FinishAsync 后,若要开始重新捕获照片,需要在调用 CaptureAsync 之前重新调用 PrepareLowLagPhotoCaptureAsync 以重新初始化捕获会话。

// Prepare and capture photo
var lowLagCapture = await mediaCapture.PrepareLowLagPhotoCaptureAsync(ImageEncodingProperties.CreateUncompressed(MediaPixelFormat.Bgra8));

var capturedPhoto = await lowLagCapture.CaptureAsync();
var softwareBitmap = capturedPhoto.Frame.SoftwareBitmap;

await lowLagCapture.FinishAsync();

自 Windows 版本 1803 起,可访问从 CaptureAsync 返回的 CapturedFrame 类的 BitmapProperties 属性,以检索与捕获的照片相关的元数据。 可将此数据传入 BitmapEncoder,进而将元数据保存到某个文件。 之前无法访问未压缩的图像格式的此数据。 还可访问 ControlValues 属性来检索描述捕获帧的控件值(如曝光度和白平衡)的 CapturedFrameControlValues 对象。

若要了解如何使用 BitmapEncoderSoftwareBitmap 对象(包括如何在 XAML 页面显示此类对象),请参阅创建、编辑和保存位图图像

若要详细了解如何设置捕获设备控件值,请参阅用于照片和视频的捕获设备控件

自 Windows 10 版本 1803 起,可通过访问 MediaCapture 返回的 CapturedFrameBitmapProperties 属性,获取以未压缩格式捕获的照片的元数据,如 EXIF 信息。 在以前的版本中,仅能在以压缩文件格式捕获的照片的标题中访问此数据。 手动写入图像文件时,可向 BitmapEncoder 提供此数据。 有关位图编码的详细信息,请参阅创建、编辑和保存位图图像。 还可通过访问 ControlValues 属性访问捕获图像时使用的帧控件值,如曝光度和闪光设置。 有关详细信息,请参阅用于照片和视频捕获的捕获设备控件

将照片捕获到文件

典型的摄影应用会将捕获的照片保存到磁盘或云存储,并且需要将元数据(例如照片方向)添加到文件。 以下示例显示如何将照片捕获到文件。 你仍可选择在以后从图像文件中创建 SoftwareBitmap

此示例中显示的技术将照片捕获到内存流,然后从该内存流将照片转码到磁盘上的某个文件。 此示例使用 GetLibraryAsync 获取用户的图片库,然后使用 SaveFolder 属性获取参考默认保存文件夹。 请记住,将图片库功能添加到应用部件清单才能访问此文件夹。 CreateFileAsync 创建保存照片的新 StorageFile

创建 InMemoryRandomAccessStream,然后调用 CapturePhotoToStreamAsync 以将照片捕获到流,该照片在流和指定应使用的图像格式的 ImageEncodingProperties 对象中传递。 可通过自行初始化对象来创建自定义编码属性,但类提供用于常见编码格式的静态方法,例如 ImageEncodingProperties.CreateJpeg。 接下来,通过调用 OpenAsync 创建输出文件的文件流。 创建 BitmapDecoder 以解码内存流中的图像,然后创建 BitmapEncoder 以通过调用 CreateForTranscodingAsync 将图像编码到文件。

可选择创建 BitmapPropertySet 对象,然后在图像编码器上调用 SetPropertiesAsync,以将照片相关元数据包含在图像文件中。 有关编码属性的详细信息,请参阅图像元数据。 大多数摄影应用需要合理处理设备方向。 有关详细信息,请参阅使用 MediaCapture 处理设备方向

最后,在编码器对象上调用 FlushAsync 以将照片从内存流转码到文件。

var myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Pictures);
StorageFile file = await myPictures.SaveFolder.CreateFileAsync("photo.jpg", CreationCollisionOption.GenerateUniqueName);

using (var captureStream = new InMemoryRandomAccessStream())
{
    await mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), captureStream);

    using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
    {
        var decoder = await BitmapDecoder.CreateAsync(captureStream);
        var encoder = await BitmapEncoder.CreateForTranscodingAsync(fileStream, decoder);

        var properties = new BitmapPropertySet {
            { "System.Photo.Orientation", new BitmapTypedValue(PhotoOrientation.Normal, PropertyType.UInt16) }
        };
        await encoder.BitmapProperties.SetPropertiesAsync(properties);

        await encoder.FlushAsync();
    }
}

若要详细了解如何使用文件和文件夹,请参阅文件、文件夹和库

捕获视频

通过使用 LowLagMediaRecording 类将视频捕获快速添加到应用。 首先,声明对象的类变量。

LowLagMediaRecording _mediaRecording;

接下来,创建将保存视频的 StorageFile 对象。 请注意,若要保存到用户的视频库(如本示例所示),必须将视频库功能添加到应用部件清单。 调用 PrepareLowLagRecordToStorageFileAsync 以初始化媒体录制,该媒体录制在存储文件和指定视频编码的 MediaEncodingProfile 对象中传递。 该类提供用于创建常见视频编码配置文件的静态方法,例如 CreateMp4

最后,调用 StartAsync 以开始捕获视频。

var myVideos = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Videos);
StorageFile file = await myVideos.SaveFolder.CreateFileAsync("video.mp4", CreationCollisionOption.GenerateUniqueName);
_mediaRecording = await mediaCapture.PrepareLowLagRecordToStorageFileAsync(
        MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto), file);
await _mediaRecording.StartAsync();

若要停止录制视频,请调用 StopAsync

await _mediaRecording.StopAsync();

可继续调用 StartAsyncStopAsync 以捕获其他视频。 完成视频捕获后,调用 FinishAsync 以释放捕获会话并清理关联的资源。 完成此调用后,必须重新调用 PrepareLowLagRecordToStorageFileAsync 以重新初始化捕获会话,然后才可 StartAsync

await _mediaRecording.FinishAsync();

在捕获视频时,应为 MediaCapture 对象的 RecordLimitationExceeded 事件注册处理程序,如果超过单次录制的时限(当前为三小时),操作系统将引发该事件。 在事件的处理程序中,应通过调用 StopAsync 完成录制。

mediaCapture.RecordLimitationExceeded += MediaCapture_RecordLimitationExceeded;
private async void MediaCapture_RecordLimitationExceeded(MediaCapture sender)
{
    await _mediaRecording.StopAsync();
    System.Diagnostics.Debug.WriteLine("Record limitation exceeded.");
}

播放和编辑捕获的视频文件

将视频捕获到文件后,你可能想要在应用的 UI 中加载并播放该文件。 可使用 MediaPlayerElement XAML 控件和关联的 MediaPlayer 执行此操作。 若要了解如何在 XAML 页面播放媒体,请参阅使用 MediaPlayer 播放音频和视频

还可通过调用 CreateFromFileAsync 从视频文件创建 MediaClip 对象。 MediaComposition 提供基本的视频编辑功能,如排列 MediaClip 对象的序列、剪裁视频长度、创建层、添加背景音乐和应用视频效果。 若要详细了解如何使用媒体合成功能,请参阅媒体合成和编辑

暂停和恢复视频录制

通过依次调用 PauseAsyncResumeAsync,可暂停并恢复视频录制,无需创建单独的输出文件。

await _mediaRecording.PauseAsync(Windows.Media.Devices.MediaCapturePauseBehavior.ReleaseHardwareResources);
await _mediaRecording.ResumeAsync();

从 Windows 10 版本 1607 开始,可暂停视频录制,并接收暂停录制前最后捕获的帧。 然后可在相机预览上覆盖此帧,以允许用户在恢复录制前将相机与暂停的帧保持一致。 调用 PauseWithResultAsync 将返回 MediaCapturePauseResult 对象。 LastFrame 属性是表示最后一帧的 VideoFrame 对象。 若要在 XAML 中显示帧,请获取视频帧的 SoftwareBitmap 表示形式。 目前仅支持预乘或空 alpha 通道且格式为 BGRA8 的图像,因此如果需要获取正确的格式,请调用 Convert。 创建新 SoftwareBitmapSource 对象,并调用 SetBitmapAsync 以对其进行初始化。 最后,设置 XAML Image 控件的 Source 属性以显示该图像。 若要使此技巧有效,图像必须与 CaptureElement 控件保持一致,并且不透明度值应小于 1。 请记住,仅可在 UI 线程上修改 UI,因为请在 RunAsync 内部进行此次调用。

PauseWithResultAsync 也会返回在上一段中录制的视频持续时间,以防你需要跟踪录制的总时长。

MediaCapturePauseResult result = 
    await _mediaRecording.PauseWithResultAsync(Windows.Media.Devices.MediaCapturePauseBehavior.RetainHardwareResources);

var pausedFrame = result.LastFrame.SoftwareBitmap;
if(pausedFrame.BitmapPixelFormat != BitmapPixelFormat.Bgra8 || pausedFrame.BitmapAlphaMode != BitmapAlphaMode.Ignore)
{
    pausedFrame = SoftwareBitmap.Convert(pausedFrame, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore);
}

var source = new SoftwareBitmapSource();
await source.SetBitmapAsync(pausedFrame);

await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
    PauseImage.Source = source;
    PauseImage.Visibility = Visibility.Visible;
});

_totalRecordedTime += result.RecordDuration;

在恢复录制时,可将图像源设置为 NULL 以将其隐藏。

await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
    PauseImage.Source = null;
    PauseImage.Visibility = Visibility.Collapsed;
});

await _mediaRecording.ResumeAsync();

请注意,在通过调用 StopWithResultAsync 停止视频时还可获得结果帧。

捕获音频

使用上述用于捕获视频的相同技术,可将音频捕获快速添加到应用。 以下示例在应用程序数据文件夹中创建 StorageFile。 调用 PrepareLowLagRecordToStorageFileAsync 以初始化捕获会话,该会话在文件和 MediaEncodingProfile(本例中由 CreateMp3 静态方法生成)中传递。 若要开始录制,请调用 StartAsync

mediaCapture.RecordLimitationExceeded += MediaCapture_RecordLimitationExceeded;

var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
StorageFile file = await localFolder.CreateFileAsync("audio.mp3", CreationCollisionOption.GenerateUniqueName);
_mediaRecording = await mediaCapture.PrepareLowLagRecordToStorageFileAsync(
        MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High), file);
await _mediaRecording.StartAsync();

若要停止音频录制,请调用 StopAsync

await _mediaRecording.StopAsync();

可多次调用 StartAsyncStopAsync 以录制多个音频文件。 完成音频捕获后,调用 FinishAsync 以释放捕获会话并清理关联的资源。 完成此调用后,必须重新调用 PrepareLowLagRecordToStorageFileAsync 以重新初始化捕获会话,然后才可 StartAsync

await _mediaRecording.FinishAsync();

检测系统的音频级别更改并做出响应

自 Windows 10 版本 1803 起,应用可检测到系统何时降低其音频捕获和音频渲染流的音频级别或何时将其静音。 例如,应用进入后台状态时,系统可能将应用的流设为静音。 AudioStateMonitor 类可用于注册以接收系统修改音频流的音量时出现的事件。 通过调用 CreateForCaptureMonitoring 获取用于监视音频捕获流的 AudioStateMonitor 实例。 通过调用 CreateForRenderMonitoring 获取用于监视音频渲染流的实例。 针对系统更改相应流类别的音频时要通知的每个监视器的 SoundLevelChanged 事件,注册一个处理程序。

// Namespaces for monitoring audio state
using Windows.Media;
using Windows.Media.Audio;
AudioStateMonitor captureAudioStateMonitor;
AudioStateMonitor renderAudioStateMonitor;
captureAudioStateMonitor = AudioStateMonitor.CreateForCaptureMonitoring();
captureAudioStateMonitor.SoundLevelChanged += CaptureAudioStateMonitor_SoundLevelChanged; ;

renderAudioStateMonitor = AudioStateMonitor.CreateForRenderMonitoring();
renderAudioStateMonitor.SoundLevelChanged += RenderAudioStateMonitor_SoundLevelChanged; ;

在捕获流的 SoundLevelChanged 处理程序中,可查看 AudioStateMonitor 发件人的 SoundLevel 属性来确定新的音量。 请注意:系统决不可让捕获流的音量降低或忽高忽低。 系统仅可将其静音或切换回最大音量。 如果音频流已静音,你可以停止正在进行的捕获。 音频流还原到最大音量后,可再次启动捕获。 以下示例使用一些布尔类变量来跟踪应用当前是否正在捕获音频以及捕获是否因音频状态而停止。 这些变量用于确定何时适合以编程方式停止或启动音频捕获。

bool isCapturingAudio = false;
bool capturingStoppedForAudioState = false;
private void CaptureAudioStateMonitor_SoundLevelChanged(AudioStateMonitor sender, object args)
{
    switch (sender.SoundLevel)
    {
        case SoundLevel.Full:
            if(capturingStoppedForAudioState)
            {
                StartAudioCapture();
                capturingStoppedForAudioState = false;
            }  
            break;
        case SoundLevel.Muted:
            if(isCapturingAudio)
            {
                StopAudioCapture();
                capturingStoppedForAudioState = true;
            }
            break;
        case SoundLevel.Low:
            // This should never happen for capture
            Debug.WriteLine("Unexpected audio state.");
            break;
    }
}

以下代码示例演示了如何将 SoundLevelChanged 处理程序用于音频渲染。 根据应用方案以及正在播放的内容类型,你可能想要在音量忽高忽低时暂停音频播放。 若要详细了解如何处理媒体播放的音量更改,请参阅使用 MediaPlayer 播放音频和视频

private void RenderAudioStateMonitor_SoundLevelChanged(AudioStateMonitor sender, object args)
{
    if ((sender.SoundLevel == SoundLevel.Full) ||
  (sender.SoundLevel == SoundLevel.Low && !isPodcast))
    {
        mediaPlayer.Play();
    }
    else if ((sender.SoundLevel == SoundLevel.Muted) ||
         (sender.SoundLevel == SoundLevel.Low && isPodcast))
    {
        // Pause playback if we’re muted or if we’re playing a podcast and are ducked
        mediaPlayer.Pause();
    }
}