メディア コンポジションと編集

この記事では、Windows.Media.Editing 名前空間の API を使って、オーディオとビデオのソース ファイルからメディア コンポジションを作成するアプリを開発する方法について説明します。 このフレームワークには、複数のビデオ クリップをまとめて追加したり、ビデオや画像のオーバーレイを追加したり、バックグラウンド オーディオを追加したり、オーディオとビデオの効果を追加したりする作業をプログラムから実行できる機能が備わっています。 作成したメディア コンポジションは、フラットなメディア ファイルにレンダリングして再生したり共有したりできるほか、コンポジションをディスクにシリアル化したりディスクから逆シリアル化したりすることもできるので、過去に作成されたコンポジションを読み込んで変更を加えるような機能をユーザーに提供することができます。 その機能はいずれも、使いやすい Windows ランタイムのインターフェイスとして用意されているため、低レベルの Microsoft メディア ファンデーション API と比べて、これらの作業を実行するために必要なコードの量や複雑さは大幅に軽減されます。

新しいメディア コンポジションを作成する

MediaComposition クラスは、コンポジションの構成要素となるすべてのメディア クリップのコンテナーで、最終的なコンポジションのレンダリングや、ディスクからの読み込みとディスクへの保存、UI に表示するプレビュー ストリームの提供などの機能を担います。 MediaComposition をアプリで使うには、Windows.Media.Editing 名前空間に加え、関連する必要な API を含んだ Windows.Media.Core 名前空間を追加する必要があります。

using Windows.Media.Editing;
using Windows.Media.Core;
using Windows.Media.Playback;
using System.Threading.Tasks;

MediaComposition オブジェクトには、コードのいたるところからアクセスすることになるため、それを格納するためのメンバー変数を宣言するのが一般的です。

private MediaComposition composition;

MediaComposition のコンストラクターに引数はありません。

composition = new MediaComposition();

コンポジションにメディア クリップを追加する

メディア コンポジションは通常、ビデオ クリップを含んでいます。 ビデオ ファイルをユーザーに選択してもらうには、FileOpenPicker を使います。 ファイルが選択されたら、MediaClip.CreateFromFileAsync を呼び出し、そのビデオ クリップを格納するための新しい MediaClip オブジェクトを作成します。 そのクリップを MediaComposition オブジェクトの Clips リストに追加します。

private async Task PickFileAndAddClip()
{
    var picker = new Windows.Storage.Pickers.FileOpenPicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
    picker.FileTypeFilter.Add(".mp4");
    Windows.Storage.StorageFile pickedFile = await picker.PickSingleFileAsync();
    if (pickedFile == null)
    {
        ShowErrorMessage("File picking cancelled");
        return;
    }

    // These files could be picked from a location that we won't have access to later
    var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
    storageItemAccessList.Add(pickedFile);

    var clip = await MediaClip.CreateFromFileAsync(pickedFile);
    composition.Clips.Add(clip);

}
  • MediaComposition には、Clips リストと同じ順序でメディア クリップが出現します。

  • MediaClip をコンポジションに追加できるのは 1 回だけです。 既にコンポジションで使われている MediaClip を追加しようとすると、エラーが発生します。 コンポジションの中でビデオ クリップを複数回にわたって再利用するには、Clone を呼び出して新しい MediaClip オブジェクトを作成し、それをコンポジションに追加してください。

  • ユニバーサル Windows アプリには、ファイル システム全体にアクセスする権限がありません。 StorageApplicationPermissions クラスの FutureAccessList プロパティを使うと、ユーザーによって選択されたファイルの記録をアプリで保存し、ファイルにアクセスするための権限を維持することができます。 FutureAccessList の最大エントリ数は 1,000 件です。リストがあふれないようアプリ側で管理する必要があります。 過去に作成されたコンポジションの読み込みと変更をサポートする場合は、この点が特に重要となります。

  • MediaComposition は、MP4 形式のビデオ クリップをサポートしています。

  • ビデオ ファイルに複数のオーディオ トラックが埋め込まれている場合、コンポジションに使うオーディオ トラックを SelectedEmbeddedAudioTrackIndex プロパティで選ぶことができます。

  • フレーム全体を単色で塗りつぶした MediaClip を作成するには、単一色とクリップの再生時間を指定して CreateFromColor を呼び出します。

  • MediaClip を画像ファイルから作成するには、画像ファイルとクリップの再生時間を指定して CreateFromImageFileAsync を呼び出します。

  • MediaClipIDirect3DSurface から作成するには、サーフェスとクリップの再生時間を指定して CreateFromSurface を呼び出します。

コンポジションを MediaElement でプレビューする

メディア コンポジションをユーザーが表示できるようにするには、UI を定義する XAML ファイルに MediaPlayerElement を追加します。

<MediaPlayerElement x:Name="mediaPlayerElement" AutoPlay="False" Margin="5" HorizontalAlignment="Stretch" AreTransportControlsEnabled="True" />

MediaStreamSource 型のメンバー変数を宣言します。

private MediaStreamSource mediaStreamSource;

MediaComposition オブジェクトの GeneratePreviewMediaStreamSource メソッドを呼び出して、コンポジションの MediaStreamSource を作成します。 ファクトリ メソッド CreateFromMediaStreamSource を呼び出して、MediaSource オブジェクトを作成し、それを MediaPlayerElementSource プロパティに割り当てます。 これでコンポジションを UI に表示することができます。

public void UpdateMediaElementSource()
{

    mediaStreamSource = composition.GeneratePreviewMediaStreamSource(
        (int)mediaPlayerElement.ActualWidth,
        (int)mediaPlayerElement.ActualHeight);

    mediaPlayerElement.Source = MediaSource.CreateFromMediaStreamSource(mediaStreamSource);

}
  • GeneratePreviewMediaStreamSource は、MediaComposition にメディア クリップが少なくとも 1 つは存在している状態で呼び出す必要があります。まったく存在しない場合、返されるオブジェクトは null になります。

  • コンポジションの変更を反映するために MediaElement のタイムラインが自動的に更新されることはありません。 コンポジションに一連の変更を行って UI の更新が必要になるたびに、GeneratePreviewMediaStreamSource を呼び出し、MediaPlayerElementSource プロパティを設定することをお勧めします。

ユーザーがページから離れたときは、MediaPlayerElementSource プロパティと MediaStreamSource オブジェクトを null に設定して、関連付けられているリソースを解放することをお勧めします。

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    mediaPlayerElement.Source = null;
    mediaStreamSource = null;
    base.OnNavigatedFrom(e);

}

コンポジションをビデオ ファイルにレンダリングする

メディア コンポジションをフラット ビデオ ファイルにレンダリングして他のデバイスで共有したり表示したりするためには、Windows.Media.Transcoding 名前空間の API が必要となります。 また、非同期操作の進行状況に応じて UI を更新するには、Windows.UI.Core 名前空間の API が必要となります。

using Windows.Media.Transcoding;
using Windows.UI.Core;

FileSavePicker でユーザーが出力ファイルを選べるようにしたら、MediaComposition オブジェクトの RenderToFileAsync を呼び出し、選択されたファイルにコンポジションをレンダリングします。 以下のコード例の残りの部分は、AsyncOperationWithProgress の処理パターンを踏襲しているだけです。

private async Task RenderCompositionToFile()
{
    var picker = new Windows.Storage.Pickers.FileSavePicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
    picker.FileTypeChoices.Add("MP4 files", new List<string>() { ".mp4" });
    picker.SuggestedFileName = "RenderedComposition.mp4";

    Windows.Storage.StorageFile file = await picker.PickSaveFileAsync();
    if (file != null)
    {
        // Call RenderToFileAsync
        var saveOperation = composition.RenderToFileAsync(file, MediaTrimmingPreference.Precise);

        saveOperation.Progress = new AsyncOperationProgressHandler<TranscodeFailureReason, double>(async (info, progress) =>
        {
            await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
            {
                ShowErrorMessage(string.Format("Saving file... Progress: {0:F0}%", progress));
            }));
        });
        saveOperation.Completed = new AsyncOperationWithProgressCompletedHandler<TranscodeFailureReason, double>(async (info, status) =>
        {
            await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
            {
                try
                {
                    var results = info.GetResults();
                    if (results != TranscodeFailureReason.None || status != AsyncStatus.Completed)
                    {
                        ShowErrorMessage("Saving was unsuccessful");
                    }
                    else
                    {
                        ShowErrorMessage("Trimmed clip saved to file");
                    }
                }
                finally
                {
                        // Update UI whether the operation succeeded or not
                    }

            }));
        });
    }
    else
    {
        ShowErrorMessage("User cancelled the file selection");
    }
}
  • トランスコード処理の速度を優先させるか、隣接するメディア クリップのトリミング精度を優先させるかは、MediaTrimmingPreference で設定することができます。 Fast を選んだ場合は、トランスコード処理の速度が上がってトリミングの精度が低下します。一方、Precise を選んだ場合は、トランスコード処理の速度が下がってトリミングの精度が向上します。

ビデオ クリップをトリミングする

コンポジションにおけるビデオ クリップの再生時間をトリミングするには、MediaClip オブジェクトの TrimTimeFromStart プロパティまたは TrimTimeFromEnd プロパティ、あるいはその両方を設定します。

private void TrimClipBeforeCurrentPosition()
{
    var currentClip = composition.Clips.FirstOrDefault(
        mc => mc.StartTimeInComposition <= mediaPlayerElement.MediaPlayer.PlaybackSession.Position &&
        mc.EndTimeInComposition >= mediaPlayerElement.MediaPlayer.PlaybackSession.Position);

    TimeSpan positionFromStart = mediaPlayerElement.MediaPlayer.PlaybackSession.Position - currentClip.StartTimeInComposition;
    currentClip.TrimTimeFromStart = positionFromStart;

}
  • ユーザーが開始トリム値と終了トリム値を指定できるようにするために必要な任意の UI を使用できます。 上の例では、MediaPlayerElement に関連付けられた MediaPlaybackSessionPosition プロパティを使い、StartTimeInCompositionEndTimeInComposition を確認することによって、コンポジションの現在位置で再生されている MediaClip を調べています。 次に、Position プロパティと StartTimeInComposition プロパティをもう一度使って、クリップの先頭からトリミングする時間を計算します。 FirstOrDefault メソッドは、System.Linq 名前空間の拡張メソッドです。リストから項目を選択するコードが、このメソッドによって単純化されます。
  • クリッピングが一切適用されていない状態のメディア クリップの再生時間は、MediaClip オブジェクトの OriginalDuration プロパティで確認できます。
  • トリミングを適用した後のメディア クリップの再生時間は、TrimmedDuration プロパティを使って確認できます。
  • クリップの元の再生時間を超えるトリミング値を指定してもエラーはスローされません。 ただし、コンポジションに含まれているクリップが 1 つだけであるときに、大きなトリミング値を指定したことによって長さがゼロにまでトリミングされた場合、それ以降の GeneratePreviewMediaStreamSource の呼び出しからは、一見コンポジションにクリップが存在しないかのように null が返されます。

コンポジションにバックグラウンド オーディオ トラックを追加する

コンポジションにバックグラウンド トラックを追加するには、オーディオ ファイルを読み込んだ後、ファクトリ メソッド BackgroundAudioTrack.CreateFromFileAsync を呼び出して BackgroundAudioTrack オブジェクトを作成します。 次に、BackgroundAudioTrack をコンポジションの BackgroundAudioTracks プロパティに追加します。

private async Task AddBackgroundAudioTrack()
{
    // Add background audio
    var picker = new Windows.Storage.Pickers.FileOpenPicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.MusicLibrary;
    picker.FileTypeFilter.Add(".mp3");
    picker.FileTypeFilter.Add(".wav");
    picker.FileTypeFilter.Add(".flac");
    Windows.Storage.StorageFile audioFile = await picker.PickSingleFileAsync();
    if (audioFile == null)
    {
        ShowErrorMessage("File picking cancelled");
        return;
    }

    // These files could be picked from a location that we won't have access to later
    var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
    storageItemAccessList.Add(audioFile);

    var backgroundTrack = await BackgroundAudioTrack.CreateFromFileAsync(audioFile);

    composition.BackgroundAudioTracks.Add(backgroundTrack);

}
  • MediaComposition では、MP3、WAV、FLAC の各形式のバックグラウンド オーディオ トラックがサポートされます。

  • バックグラウンド オーディオ トラック。

  • ビデオ ファイルの場合と同様、StorageApplicationPermissions クラスを使ってコンポジション内のファイルにアクセスする権限を維持することをお勧めします。

  • MediaClip の場合と同様、BackgroundAudioTrack をコンポジションに追加できるのは 1 回だけです。 既にコンポジションで使われている BackgroundAudioTrack を追加しようとすると、エラーが発生します。 コンポジションの中でオーディオ トラックを複数回にわたって再利用するには、Clone を呼び出して新しい MediaClip オブジェクトを作成し、それをコンポジションに追加してください。

  • 既定では、コンポジションの開始時にバックグラウンド オーディオ トラックが再生されます。 複数のバックグラウンド トラックが存在する場合、コンポジションの開始時にすべてのトラックの再生が開始されます。 バックグラウンド オーディオ トラックの再生を他のタイミングで開始するには、Delay プロパティに、目的のタイム オフセットを設定してください。

コンポジションにオーバーレイを追加する

オーバーレイを使うと、コンポジションの複数のビデオ レイヤーを重ね合わせることができます。 コンポジションには、複数のオーバーレイ レイヤーを含めることができ、それぞれのオーバーレイ レイヤーには複数のオーバーレイを追加することができます。 MediaOverlay オブジェクトは、そのコンストラクターに MediaClip を渡すことによって作成します。 オーバーレイの位置と不透明度を設定したら、新しい MediaOverlayLayer を作成し、その Overlays リストに MediaOverlay を追加します。 最後に、その MediaOverlayLayer をコンポジションの OverlayLayers リストに追加します。

private void AddOverlay(MediaClip overlayMediaClip, double scale, double left, double top, double opacity)
{
    Windows.Media.MediaProperties.VideoEncodingProperties encodingProperties =
        overlayMediaClip.GetVideoEncodingProperties();

    Rect overlayPosition;

    overlayPosition.Width = (double)encodingProperties.Width * scale;
    overlayPosition.Height = (double)encodingProperties.Height * scale;
    overlayPosition.X = left;
    overlayPosition.Y = top;

    MediaOverlay mediaOverlay = new MediaOverlay(overlayMediaClip);
    mediaOverlay.Position = overlayPosition;
    mediaOverlay.Opacity = opacity;

    MediaOverlayLayer mediaOverlayLayer = new MediaOverlayLayer();
    mediaOverlayLayer.Overlays.Add(mediaOverlay);

    composition.OverlayLayers.Add(mediaOverlayLayer);
}
  • レイヤーにおけるオーバーレイの Z オーダーは、そのオーバーレイを含んでいるレイヤーの Overlays リストにおける順序に基づいて決まります。 リストにおけるインデックスが大きいほど、手前にレンダリングされます。 コンポジションにおけるオーバーレイ レイヤーにも同じことが当てはまります。 コンポジションの OverlayLayers リストにおけるインデックスが大きいレイヤーほど、手前にレンダリングされます。

  • オーバーレイは、順に再生されるのではなく上に積み重ねられるため、既定ではコンポジションの開始と共にすべてのオーバーレイが再生されます。 オーバーレイの再生を他のタイミングで開始するには、Delay プロパティに、目的のタイム オフセットを設定してください。

メディア クリップに効果を追加する

コンポジションに含まれる各 MediaClip には、オーディオ効果とビデオ効果のリストがあって、そのリストに複数の効果を追加することができます。 これらの効果にはそれぞれ IAudioEffectDefinition または IVideoEffectDefinition が実装されている必要があります。 次の例は、現在の MediaPlayerElement 位置を使って表示されている MediaClip を選び、VideoStabilizationEffectDefinition の新しいインスタンスを作成して、メディア クリップの VideoEffectDefinitions リストに追加しています。

private void AddVideoEffect()
{
    var currentClip = composition.Clips.FirstOrDefault(
        mc => mc.StartTimeInComposition <= mediaPlayerElement.MediaPlayer.PlaybackSession.Position &&
        mc.EndTimeInComposition >= mediaPlayerElement.MediaPlayer.PlaybackSession.Position);

    VideoStabilizationEffectDefinition videoEffect = new VideoStabilizationEffectDefinition();
    currentClip.VideoEffectDefinitions.Add(videoEffect);
}

コンポジションをファイルに保存する

メディア コンポジションは、後で変更を加えることができるようにファイルにシリアル化することができます。 出力ファイルを選んだ後、MediaCompositionSaveAsync メソッドを呼び出してコンポジションを保存します。

private async Task SaveComposition()
{
    var picker = new Windows.Storage.Pickers.FileSavePicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
    picker.FileTypeChoices.Add("Composition files", new List<string>() { ".cmp" });
    picker.SuggestedFileName = "SavedComposition";

    Windows.Storage.StorageFile compositionFile = await picker.PickSaveFileAsync();
    if (compositionFile == null)
    {
        ShowErrorMessage("User cancelled the file selection");
    }
    else
    {
        var action = composition.SaveAsync(compositionFile);
        action.Completed = (info, status) =>
        {
            if (status != AsyncStatus.Completed)
            {
                ShowErrorMessage("Error saving composition");
            }

        };
    }
}

コンポジションをファイルから読み込む

メディア コンポジションをファイルから逆シリアル化することによって、コンポジションを表示したり変更を加えたりする機能をユーザーに提供することができます。 コンポジション ファイルを選んだ後、MediaCompositionLoadAsync メソッドを呼び出してコンポジションを読み込みます。

private async Task OpenComposition()
{
    var picker = new Windows.Storage.Pickers.FileOpenPicker();
    picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
    picker.FileTypeFilter.Add(".cmp");

    Windows.Storage.StorageFile compositionFile = await picker.PickSingleFileAsync();
    if (compositionFile == null)
    {
        ShowErrorMessage("File picking cancelled");
    }
    else
    {
        composition = null;
        composition = await MediaComposition.LoadAsync(compositionFile);

        if (composition != null)
        {
            UpdateMediaElementSource();

        }
        else
        {
            ShowErrorMessage("Unable to open composition");
        }
    }
}
  • コンポジション内のメディア ファイルが、アプリがアクセスできる場所になく、アプリの StorageApplicationPermissions クラスの FutureAccessList プロパティに存在しない場合、コンポジションの読み込み時にエラーがスローされます。