次の方法で共有



June 2016

Volume 31 Number 6

最新のアプリ - UWP でのオーディオの操作

Frank La La

ユニバーサル Windows プラットフォーム (UWP) には、オーディオの録音やビデオの録画のためのリッチな API が用意されています。ただし、含まれる機能セットは、単なる録音や録画だけではありません。数行のコードだけで、オーディオに特殊効果をリアルタイムに施すことができます。リバーブ、エコーなどの効果が API に組み込まれ、実に簡単に実装できるようになっています。今回は、オーディオの録音と特殊効果の適用の基礎をいくつか取り上げます。その基礎を説明しながら、オーディオを録音して保存し、さまざまなフィルターや特殊効果を施す UWP アプリを作成します。

オーディオを録音するプロジェクトのセットアップ

オーディオを録音するには、アプリにマイクへのアクセス許可が必要です。そこで、アプリのマニフェスト ファイルを変更します。ソリューション エクスプローラーで、Package.appxmanifest ファイルをダブルクリックします。このファイルは、通常、プロジェクトのルートにあります。

アプリのマニフェスト ファイルがエディター ウィンドウで開いたら、[機能] タブをクリックします。[機能] ボックスの一覧で、マイク機能をチェックします。これにより、アプリからエンド ユーザーのマイクにアクセスできるようになります。これを行わずにマイクにアクセスしようとすると、アプリは例外をスローします。

オーディオの録音

オーディオに特殊効果を施す前に、まずオーディオを録音できるようにします。これはかなり簡単です。最初に、オーディオ録音のすべてのコードをカプセル化するクラスをプロジェクトに追加します。このクラスを AudioRecorder とします。このクラスには、録音の開始と停止を行うパブリック メソッドや、録音直後のオーディオ クリップを再生するパブリック メソッドを含めます。そこで、このクラスにいくつかメンバーを追加します。最初は MediaCapture で、マイクや Web カメラなどのキャプチャ デバイスから、オーディオ、ビデオ、画像を取り込む機能を提供します。

private MediaCapture _mediaCapture;

次に、InMemoryRandomAccessStream を追加して、マイクの入力をメモリに取り込みます。

private InMemoryRandomAccessStream _memoryBuffer;

録音状態を追跡するために、パブリックにアクセスできる Boolean プロパティをクラスに追加します。

public bool IsRecording { get; set; }

オーディオを録音する場合、既に録音中かどうかをチェックする必要があります。録音中であれば例外をスローします。録音中でなければ、メモリ ストリームを初期化し、以前の録音ファイルを削除してから録音を開始します。

MediaCapture クラスは複数の関数を提供するため、オーディオを取り込む関数を指定しなければなりません。そこで、MediaCaptureInitializationSettings のインスタンスを作成します。次に、MediaCapture オブジェクトのインスタンスを作成し、MediaCaptureInitializationSettings を InitializeAsync メソッドに渡します (図 1 参照)。

図 1 MediaCapture オブジェクトのインスタンス作成

public async void Record()
  {
  if (IsRecording)
  {
    throw new InvalidOperationException("Recording already in progress!");
  }
  await Initialize();
  await DeleteExistingFile();
  MediaCaptureInitializationSettings settings =
    new MediaCaptureInitializationSettings
  {
    StreamingCaptureMode = StreamingCaptureMode.Audio
  };
  _mediaCapture = new MediaCapture();
  await _mediaCapture.InitializeAsync(settings);
  await _mediaCapture.StartRecordToStreamAsync(
    MediaEncodingProfile.CreateMp3(AudioEncodingQuality.Auto), _memoryBuffer);
  IsRecording = true;
}

最後に、MP3 形式での録音とデータの保存先をパラメーターで渡して、MediaCapture オブジェクトに録音開始を指示します。

録音を停止する場合は、次の数行のコードが必要です。

public async void StopRecording()
{
  await _mediaCapture.StopRecordAsync();
  IsRecording = false;
  SaveAudioToFile();
}

StopRecording メソッドは、MediaCapture オブジェクトへ録音停止を指示し、録音状態を false に設定して、オーディオ ストリーム データをディスク上の MP3 ファイルに保存します。

オーディオ データのディスクへの保存

オーディオ データを InMemoryRandomAccessStream に取り込んだら、そのコンテンツをディスクに保存します (図 2 参照)。メモリ内ストリームからオーディオ データを保存するには、コンテンツを別のストリームにコピーしてから、ディスクにそのデータをプッシュします。Windows.ApplicationModel.Package 名前空間のユーティリティを使用して、アプリのインストール ディレクトリへのパスを取得します (開発中は、プロジェクトの \bin\x86\Debug ディレクトリ)。ここが、録音ファイルの保存場所です。コードを簡単に変更して、別の場所に保存したり、ユーザーにファイルの保存先を選択してもらうこともできます。

図 2 オーディオ データのディスクへの保存

private async void SaveAudioToFile()
{
  IRandomAccessStream audioStream = _memoryBuffer.CloneStream();
  StorageFolder storageFolder = Package.Current.InstalledLocation;
  StorageFile storageFile = await storageFolder.CreateFileAsync(
    DEFAULT_AUDIO_FILENAME, CreationCollisionOption.GenerateUniqueName);
  this._fileName = storageFile.Name;
  using (IRandomAccessStream fileStream =
    await storageFile.OpenAsync(FileAccessMode.ReadWrite))
  {
    await RandomAccessStream.CopyAndCloseAsync(
      audioStream.GetInputStreamAt(0), fileStream.GetOutputStreamAt(0));
    await audioStream.FlushAsync();
    audioStream.Dispose();
  }
}

オーディオの再生

この時点でオーディオ データはメモリ内のバッファーにもディスク上にもあるため、メモリとディスクのどちらから再生するかを選択します。

メモリからオーディオを再生するコードは非常にシンプルです。MediaElement コントロールの新しいインスタンスを作成し、ソースをメモリ内バッファーに設定して、MIME の種類を渡し、Play メソッドを呼び出します。

public void Play()
{
  MediaElement playbackMediaElement = new MediaElement();
  playbackMediaElement.SetSource(_memoryBuffer, "MP3");
  playbackMediaElement.Play();
}

ディスクから再生する場合、ファイルを非同期に開くタスクを用意するため、簡単なコードの追加が必要になります。UI スレッドからタスクを実行する他のスレッドと通信するため、CoreDispatcher を使用する必要があります。CoreDispatcher は、特定のコード部分を実行するスレッドと UI スレッド間でメッセージを送信します。これにより、コードは別のスレッドから UI コンテキストを取得できるようになります。CoreDispatcher の優れたコーディングについては、David Crook のブログ記事 (bit.ly/1SbJ6up、英語) をご覧ください。

非同期コードを処理する追加の行を除けば、メソッドのコードはメモリ内バッファーを使用する場合のメソッドとほぼ同じです。

public async Task PlayFromDisk(CoreDispatcher dispatcher)
{
  await dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  {
    MediaElement playbackMediaElement = new MediaElement();
    StorageFolder storageFolder = Package.Current.InstalledLocation;
    StorageFile storageFile = await storageFolder.GetFileAsync(this._fileName);
    IRandomAccessStream stream = await storageFile.OpenAsync(FileAccessMode.Read);
    playbackMediaElement.SetSource(stream, storageFile.FileType);
    playbackMediaElement.Play();
  });
}

UI のビルド

AudioRecorder クラスが完成したら、残りはアプリのインターフェイスのビルドだけです。このプロジェクトのインターフェイスは非常にシンプルです。必要なのは、録音ボタンと録音したオーディオの再生ボタンだけです (図 3 参照)。したがって、XAML もシンプルです。TextBlock と 2 つのボタンを含むスタック パネルを用意するだけです。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <Grid.RowDefinitions>
    <RowDefinition Height="43"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
<TextBlock FontSize="24">Audio in UWP</TextBlock>
<StackPanel HorizontalAlignment="Center" Grid.Row="1" >
  <Button Name="btnRecord" Click="btnRecord_Click">Record</Button>
  <Button Name="btnPlay" Click="btnPlay_Click">Play</Button>
</StackPanel>
</Grid>

AudioRecorder の UI
図 3 AudioRecorder の UI

分離コード クラスで、AudioRecorder のメンバー変数を作成します。これは、オーディオの録音と再生のために、アプリが使用するオブジェクトです。

AudioRecorder _audioRecorder;

アプリの MainPage のコンストラクターで AudioRecorder クラスのインスタンスを作成します。

public MainPage()
{
  this.InitializeComponent();
  this._audioRecorder = new AudioRecorder();
}

btnRecord ボタンは、実際には、オーディオ録音の開始と停止で切り替えます。AudioRecorder の現在状態をユーザーに通知するため、btnRecordClick メソッドは録音の開始と停止だけでなく、btnRecord ボタンのコンテンツも変更します。

btnPlay ボタンのイベント ハンドラーには、メモリ内バッファーから再生するか、ディスクに保存されたファイルから再生するかを決めるオプションがあります。

メモリ内バッファーから再生するコードは簡単です。

private void btnPlay_Click(object sender, RoutedEventArgs e)
{
  this._audioRecorder.Play();
}

前述のように、ディスクからのファイルの再生は非同期に行います。そのためには、タスクを UI スレッド以外の別のスレッドで実行します。OS スケジューラーは、タスクを実行するスレッドを実行時に決定します。Dispatcher オブジェクトを PlayFromDisk メソッドに渡すことで、そのスレッドから UI スレッドの UI コンテキストにアクセスできるようになります。

private async void btnPlay_Click(object sender, RoutedEventArgs e)
{
  await this._audioRecorder.PlayFromDisk(Dispatcher);
}

特殊効果の適用

これで、オーディオの録音と再生を行うアプリが完成したので、あまり知られていない UWP の機能を見ていくことにします。それが、リアルタイム オーディオ特殊効果です。Windows.Media.Audio 名前空間の API には、アプリに新たな感触を加えるさまざまな特殊効果が含まれています。

今回のプロジェクトでは、特殊効果のコードはすべて独自のクラスに追加することにします。ただし、新しいクラスを作成する前に、AudioRecorder クラスに最後の変更を 1 つ加えておきます。追加するのは次のメソッドです。

public async Task<StorageFile>
   GetStorageFile(CoreDispatcher dispatcher)
{
  StorageFolder storageFolder =
    Package.Current.InstalledLocation;
  StorageFile storageFile =
    await storageFolder.GetFileAsync(this._fileName);
  return storageFile;
}

GetStorageFile メソッドは、保存済みのオーディオ ファイルを指す StorageFile オブジェクトを返します。特殊効果のクラスは、このオブジェクトを使ってオーディオ データにアクセスすることになります。

AudioGraph の概要

AudioGraph クラスが、UWP の高度なオーディオ シナリオの中心です。AudioGraph は、さまざまに入り組んだノードを経由して、入力ソース ノードから出力ソース ノードまでオーディオ データをルーティングします。今回は、AudioGraph の機能をすべて取り上げることができませんが、次回以降のコラムで詳しく紹介する予定です。今回の重要ポイントは、オーディオ グラフのどのノードにも複数のオーディオ効果を適用できることです。AudioGraph の詳細については、Windows デベロッパー センターの資料 (msdn.microsoft.com/ja-jp/windows/uwp/audio-video-camera/audio-graphs) をご覧ください。

まず、AudioEffects というクラスをプロジェクトに追加して、次のメンバーを追加します。

private AudioGraph _audioGraph;
private AudioFileInputNode _fileInputNode;
private AudioDeviceOutputNode _deviceOutputNode;

AudioGraph クラスのインスタンスを作成するには、AudioGraph 用の構成設定を含む AudioGraphSettings オブジェクトを作成する必要があります。次に、これらの構成設定を渡して AudioGraph.CreateAsync メソッドを呼び出します。CreateAsync メソッドは CreateAudioGraphResult オブジェクトを返します。このクラスは、作成したオーディオ グラフへのアクセスと、オーディオ グラフの作成に失敗したか成功したかを示すステータス値を提供します。

また、オーディオを再生するための出力ノードも作成する必要があります。これを行うには、AudioGraph クラスの CreateDeviceOutputNodeAsync メソッドを呼び出し、メンバー変数を CreateAudioDeviceOutputNodeResult の DeviceOutputNode プロパティに設定します。AudioGraph と AudioDeviceOutputNode を初期化するコードはすべて、InitializeAudioGraph メソッド内に配置します。

public async Task InitializeAudioGraph()
{
  AudioGraphSettings settings = new AudioGraphSettings(AudioRenderCategory.Media);
  CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
  this._audioGraph = result.Graph;
  CreateAudioDeviceOutputNodeResult outputDeviceNodeResult =
    await this._audioGraph.CreateDeviceOutputNodeAsync();
  _deviceOutputNode = outputDeviceNodeResult.DeviceOutputNode;
}

AudioGraph オブジェクトからのオーディオ再生は簡単で、Play メソッドを呼び出すだけです。AudioGraph は AudioEffects クラスのプライベート メンバーなので、アクセス可能にするため、次のようにパブリック メソッドにラップします。

public void Play()
{
this._audioGraph.Start();
}

AudioGraph に出力デバイス ノードを作成したら、今度はディスクに保存されているオーディオ ファイルからの入力ノードを作成します。また、FileInputNode への発信接続も追加します。今回の場合は、発信ノードをオーディオ出力デバイスにします。LoadFileIntoGraph メソッドでこの処理を行います。

public async Task LoadFileIntoGraph(StorageFile audioFile)
{
  CreateAudioFileInputNodeResult audioFileInputResult =
    await this._audioGraph.CreateFileInputNodeAsync(audioFile);
  _fileInputNode = audioFileInputResult.FileInputNode;
  _fileInputNode.AddOutgoingConnection(_deviceOutputNode);
  CreateAndAddEchoEffect();
}

CreateAndAddEchoEffect メソッドへの参照も必要です。これについては後ほど説明します。

オーディオ効果の追加

オーディオ グラフ API には、エコー、リバーブ、イコライザー、リミッターという 4 つの組み込みオーディオ効果があります。今回は、録音済みのサウンドにエコーを追加します。この効果の追加は簡単で、EchoEffectDefition オブジェクトを作成して効果のプロパティを設定するだけです。作成が済んだら、ノードに効果の定義を追加します。今回は、録音してディスクに保存したオーディオ データを含む _fileInputNode に効果を追加します。

private void CreateAndAddEchoEffect()
{
  EchoEffectDefinition echoEffectDefinition = new EchoEffectDefinition(this._audioGraph);
  echoEffectDefinition.Delay = 100.0f;
  echoEffectDefinition.WetDryMix = 0.7f;
  echoEffectDefinition.Feedback = 0.5f;
  _fileInputNode.EffectDefinitions.Add(echoEffectDefinition);
}

すべてを 1 つに組み立てる

AudioEffect クラスが完成したところで、UI からこのクラスを使用します。まず、アプリのメイン ページにボタンを 1 つ追加します。

<Button Content="Play with Special Effect" Click="btnSpecialEffectPlay_Click" />

Click イベントのハンドラー内で、オーディオ データを格納したファイルを取得し、AudioEffects クラスのインスタンスを作成してオーディオ データ ファイルに渡します。すべてを終えたら、Play メソッドを呼び出して、サウンドを再生します。

private async void btnSpecialEffectPlay_Click(object sender, RoutedEventArgs e)
{
  var storageFile = await this._audioRecorder.GetStorageFile(Dispatcher);
  AudioEffects effects = new AudioEffects();
  await effects.InitializeAudioGraph();
  await effects.LoadFileIntoGraph(storageFile);
  effects.Play();
}

アプリを実行し、[Record] をクリックして簡単なを録音します。録音したオーディオをそのまま聞くには、[Play] をクリックします。同じオーディオにエコーを加えて聞くには、[Play with Special Effect] をクリックします。

まとめ

UWP には、オーディオの取り込みようのリッチなサポートだけでなく、メディアに特殊効果をリアルタイムに適用する優れた機能がいくつかあります。プラットフォームには、オーディオに適用できるさまざまな効果が組み込まれています。エコー、リバーブ、イコライザー、リミッターなどがその例です。効果は、個別に適用することも、いくつか組み合わせて適用することもできます。枠にとらわれず、自由に発想してください。


Frank La Vigne は、Microsoft Technology and Civic Engagement チームのテクノロジ エバンジェリストで、より良いコミュニティを形成するためにユーザーによるテクノロジの活用を支援しています。彼は FranksWorld.com (英語) に定期的にブログを投稿し、Frank's World TV (youtube.com/FranksWorldTV、英語) という YouTube チャンネルを主催しています。

この記事のレビューに協力してくれた技術スタッフの Drew Batchelor および Jose Luis Manners に心より感謝いたします。