MediaCapture를 사용하여 기본적인 사진, 비디오 및 오디오 캡처

이 문서에서는 MediaCapture 클래스를 사용하여 사진과 비디오를 캡처하는 가장 간단한 방법을 보여줍니다. MediaCapture 클래스는 캡처 파이프라인에 대해 낮은 수준의 제어를 제공하고 고급 캡처 시나리오를 사용하도록 설정하는 강력한 API 집합을 노출하지만, 이 문서는 앱에 기본 미디어 캡처를 빠르고 쉽게 추가할 수 있도록 하기 위한 것입니다. MediaCapture에서 제공하는 기능에 대한 자세한 내용은 Camera를 참조하세요.

단순히 사진이나 비디오를 캡처하고 추가 미디어 캡처 기능을 추가하지 않으려는 경우 또는 사용자 고유의 카메라 UI를 만들지 않으려는 경우 windows 기본 제공 카메라 앱을 시작하고 캡처된 사진 또는 비디오 파일을 받을 수 있는 CameraCaptureUI 클래스를 사용할 수 있습니다. 자세한 내용은 Windows 기본 제공 카메라 UI를 사용하여 사진 및 비디오 캡처를 참조하세요.

이 문서의 코드는 카메라 시작 키트 샘플에서 조정되었습니다. 샘플을 다운로드하여 컨텍스트에서 사용된 코드를 보거나 사용자 고유의 앱의 시작점으로 사용할 수 있습니다.

앱 매니페스트에 기능 선언 추가

앱이 디바이스의 카메라에 액세스하려면 앱이 웹캠마이크 디바이스 기능을 사용한다고 선언해야 합니다. 캡처한 사진과 비디오를 사용자의 사진 또는 비디오 라이브러리에 저장하려면 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에 사진 캡처

SoftwareBitmap 클래스는 여러 기능에서 이미지의 일반적인 표현을 제공하기 위해 Windows 10에 도입되었습니다. 사진을 캡처한 다음 캡처한 이미지를 파일에 캡처하는 대신 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 개체를 검색할 수 있습니다.

BitmapEncoder 사용 방법뿐 아니라 XAML 페이지에 표시하는 방법 등 SoftwareBitmap 개체 작업에 대한 자세한 내용은 비트맵 이미지 만들기, 편집 및 저장을 참조하세요.

캡처 디바이스 제어 값 설정에 대한 자세한 내용은 사진 및 비디오에 대한 디바이스 컨트롤 캡처를 참조하세요.

Windows 10, 버전 1803부터는 압축되지 않은 형식으로 캡처된 사진에 대해 EXIF 정보와 같은 메타데이터를 얻기 위해서는 MediaCapture에서 반환한 CapturedFrameBitmapProperties 속성에 액세스해야 합니다. 이전 버전에서는 압축 파일 형식으로 캡처된 사진의 헤더에서만 이 데이터를 액세스할 수 있었습니다. 이미지 파일에 수동으로 작성할 때 이 데이터를 BitmapEncoder에 제공할 수 있습니다. 인코딩 비트맵에 대한 자세한 내용은 비트맵 이미지 만들기, 편집 및 저장을 참조하세요. 또한 노출과 플래시 설정과 같은 프레임 제어 값에 액세스할 수 있으며, 이것은 ControlValues 속성에 액세스하여 이미지를 캡처할 때 사용된 것입니다. 자세한 내용은 사진과 비디오 캡처를 위한 캡처 디바이스 컨트롤을 참조하세요.

파일에 사진 캡처

일반적인 사진 앱은 캡처한 사진을 디스크 또는 클라우드 스토리지에 저장하고 사진 방향과 같은 메타데이터를 파일에 추가해야 합니다. 다음 예제에서는 파일에 사진을 캡처하는 방법을 보여줍니다. 나중에 이미지 파일에서 SoftwareBitmap을 만들 수 있습니다.

이 예제에 표시된 기술은 사진을 메모리 내 스트림으로 캡처한 다음, 스트림에서 디스크의 파일로 사진을 트랜스코딩합니다. 이 예제에서는 GetLibraryAsync를 사용하여 사용자의 그림 라이브러리를 가져오는 다음 SaveFolder 속성을 사용하여 참조 기본 저장 폴더를 가져옵니다. 이 폴더에 액세스하려면 앱 매니페스트에 그림 라이브러리 기능을 추가해야 합니다. CreateFileAsync는 사진이 저장되는 새 StorageFile을 만듭니다.

InMemoryRandomAccessStream을 만든 다음 CapturePhotoToStreamAsync를 호출하여 스트림에 사진을 캡처하고 스트림 및 사용해야 하는 이미지 형식을 지정하는 ImageEncodingProperties 개체를 전달합니다. 개체를 직접 초기화하여 사용자 지정 인코딩 속성을 만들 수 있지만 클래스는 일반적인 인코딩 형식에 대해 ImageEncodingProperties.CreateJpeg와 같은 정적 메서드를 제공합니다. 다음으로 OpenAsync를 호출하여 출력 파일에 파일 스트림을 만듭니다. 메모리 스트림에서 이미지를 디코딩하는 BitmapDecoder를 만든 다음 CreateForTranscodingAsync를 호출하여 이미지를 파일에 인코딩하는 BitmapEncoder를 만듭니다.

필요에 따라 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에 전화하여 캡처 세션을 삭제하고 연결된 리소스를 클린. 이 호출 후에는 StartAsync를 호출하기 전에 PrepareLowLagRecordToStorageFileAsync를 다시 호출하여 캡처 세션을 다시 초기화해야 합니다.

await _mediaRecording.FinishAsync();

비디오를 캡처할 때 MediaCapture 개체의 RecordLimitationExceeded 이벤트에 대한 처리기를 등록해야 합니다. 이 이벤트는 현재 3시간 동안 단일 녹화에 대한 제한을 초과하는 경우 운영 체제에서 발생합니다. 이벤트 처리기에서 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를 사용하여 오디오 및 비디오 재생를 참조하세요.

비디오 파일에서 MediaClip 개체를 만들려면 CreateFromFileAsync를 호출합니다. MediaCompositionMediaClip 개체의 시퀀스 정렬, 비디오 길이 자르기, 레이어 만들기, 백그라운드 음악 추가, 비디오 효과 적용과 같은 기본 비디오 편집 기능을 제공합니다. 미디어 컴퍼지션 작업에 대한 자세한 내용은 미디어 컴퍼지션 및 편집을 참조하세요.

비디오 녹화 일시 중지 및 다시 시작

PauseAsync를 호출한 다음 ResumeAsync를 호출하여 별도의 출력 파일을 만들지 않고 비디오 녹화를 일시 중지한 다음 녹화를 다시 시작할 수 있습니다.

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

Windows 10 버전 1607부터 비디오 녹화를 일시 중지하고 녹화가 일시 중지되기 전에 캡처된 마지막 프레임을 받을 수 있습니다. 그런 다음 카메라 미리 보기에서 이 프레임을 오버레이하여 사용자가 녹화를 다시 시작하기 전에 일시 중지된 프레임에 카메라를 맞출 수 있습니다. PauseWithResultAsync를 호출하면 MediaCapturePauseResult 개체가 반환됩니다. LastFrame 속성은 마지막 프레임을 나타내는 VideoFrame 개체입니다. XAML에서 프레임을 표시하려면 비디오 프레임의 SoftwareBitmap 표현을 가져옵니다. 현재 미리 곱한 알파 채널 또는 빈 알파 채널이 있는 BGRA8 형식의 이미지만 지원되므로 필요한 경우 Convert를 호출하여 올바른 형식을 가져옵니다. 새 SoftwareBitmapSource 개체를 만들고 SetBitmapAsync를 호출하여 초기화합니다. 마지막으로 XAML 이미지 컨트롤의 Source 속성을 설정하여 Image를 표시합니다. 이 트릭이 작동하려면 이미지를 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를 호출하여 캡처 세션을 초기화하고 CreateMp3 정적 메서드에 의해 이 예제에서 생성된 파일 및 MediaEncodingProfile을 전달합니다. 녹음/녹화를 시작하려면 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을 호출하여 캡처 세션을 삭제하고 연결된 리소스를 정리하세요. 이 호출 후에는 StartAsync를 호출하기 전에 PrepareLowLagRecordToStorageFileAsync를 다시 호출하여 캡처 세션을 다시 초기화해야 합니다.

await _mediaRecording.FinishAsync();

시스템을 통해 오디오 레벨 변경 검색 및 대응

Windows 10 버전 1803부터는 시스템이 앱의 오디오 캡처 및 오디오 렌더링 스트림의 오디오 레벨을 낮추거나 음을 소거하면 앱이 이를 검색할 수 있습니다. 예를 들어 시스템이 백그라운드로 실행하기 시작할 때 앱의 스트림 음을 소거할 수 있습니다. AudioStateMonitor 클래스를 사용하면 시스템이 오디오 스트림의 볼륨을 수정할 때 이벤트를 수신하도록 등록할 수 있습니다. 오디오 캡처 스트림을 모니터링하기 위해 AudioStateMonitor 인스턴스를 가져오려면 CreateForCaptureMonitoring을 호출합니다. 오디오 렌더 스트림을 모니터링하기 위해 인스턴스를 가져오려면 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 속성을 확인하여 새로운 소리 레벨을 결정할 수 있습니다. 시스템이 캡처 스트림을 낮추거나 '더킹(ducked)'해서는 안 됩니다. 음을 소거하거나 전체 볼륨으로 다시 전환해야 합니다. 오디오 스트림 음이 소거된 경우 진행 중인 캡처를 중지할 수 있습니다. 오디오 스트림이 전체 볼륨으로 복원되면 다시 캡처를 시작할 수 있습니다. 다음 예에서는 일부 부울 클래스 변수를 사용하여 앱에서 현재 오디오를 캡처하고 있는지, 그리고 오디오 상태가 변경되어 캡처가 중지되었는지를 추적합니다. 이러한 변수를 사용하여 프로그래밍 방식으로 오디오 캡처를 중지하거나 시작하는 것이 적절한지 판단할 수 있습니다.

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