2016 年六月

第 31 卷,第 6 期

本文章是由機器翻譯。

新式應用程式 - 在 UWP 中運用音訊

Frank La La

通用 Windows 平台 (UWP) 具有豐富的 Api,錄製音訊和視訊。不過,功能集不會停止在錄製。只需要幾行程式碼,開發人員可以即時音訊套用特殊效果。效果回響和回應等內建 API,因此很容易實作。在本文中,我將探討一些基本的音訊錄製和套用特殊效果。我將建立 UWP 應用程式來錄製音訊,並儲存它,套用不同的篩選條件和特殊效果。

設定要錄製音訊的專案

錄製音訊需要應用程式,以有權存取麥克風,並且需要修改應用程式的資訊清單檔案。在 [方案總管] 中按兩下 Package.appxmanifest 檔案。它永遠是在-專案的根目錄。

一旦應用程式資訊清單檔編輯器] 視窗隨即開啟,按一下 [功能] 索引標籤。在功能清單方塊中,檢查麥克風功能。這可讓您的應用程式存取使用者的麥克風。沒有這麼做,您的應用程式會擲回例外狀況,當您嘗試存取麥克風。

錄製音訊

加入音訊的特殊效果開始之前,您首先要能夠錄製音訊。這是相當簡單。首先,將類別加入至封裝的音訊錄製程式碼專案。您會呼叫這個類別 AudioRecorder。它會有公用的方法來啟動和停止錄製,以及播放您錄製的音訊剪輯。若要這樣做,您必須將某些成員加入至您的類別。第一個會是 MediaCapture,可提供擷取音訊、 視訊和影像擷取裝置,例如網路攝影機或麥克風的功能 ︰

private MediaCapture _mediaCapture;

您也需要加入 InMemoryRandomAccessStream 擷取至記憶體的麥克風的輸入 ︰

private InMemoryRandomAccessStream _memoryBuffer;

若要追蹤的錄製的狀態,您需要新增到您的類別的可公開存取屬性的布林值 ︰

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

最後,您會告訴 MediaCapture 物件,若要開始錄製、 傳遞參數,它會記錄 MP3 格式和位置來儲存資料。

停止錄製需要最少的程式碼 ︰

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 臂在主體上的部落格文章 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 和兩個按鈕堆疊面板 ︰

<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 的目前狀態,btnRecord_Click 方法 btnRecord 按鈕的內容變更,以及啟動和停止錄製。

您有兩個 btnPlay 按鈕的事件處理常式的選項 ︰ 從記憶體中緩衝區播放或播放儲存在磁碟上的檔案中。

若要從記憶體中緩衝區播放,程式碼很簡單 ︰

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

如先前所述,播放從磁碟檔案會以非同步的方式。這表示工作會執行不同的執行緒與 UI 執行緒上。OS 排程器將會決定哪些此工作會在執行階段執行的執行緒。將發送器物件傳遞至 PlayFromDisk 方法,可讓執行緒存取 UI 執行緒的 UI 內容 ︰

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

套用特殊效果

現在,您有應用程式錄製和播放音訊,回顧探索 UWP 鮮為人知的功能 ︰ 即時音訊的特殊效果。Windows.Media.Audio 命名空間中的 Api 中是可以將額外的觸控式加入至應用程式的特殊效果。

此專案中,您會將所有的特殊效果程式碼放在它自己的類別。不過,您建立新的類別之前,您要進行一個 AudioRecorder 類別的最後一次修改。我要加入下列方法 ︰

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 開發人員中心,在文件 bit.ly/1VCIBfD

首先,您會想要加入至您的專案呼叫 AudioEffects 類別並新增下列成員 ︰

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

若要建立 AudioGraph 類別的執行個體,您必須建立 AudioGraphSettings 物件,其中包含 AudioGraph 的組態設定。然後,您會呼叫 AudioGraph.CreateAsync 方法傳遞這些組態設定。CreateAsync 方法會傳回 CreateAudioGraphResult 物件。這個類別會提供存取建立音訊圖形和狀態值是否建立音訊圖形為失敗或成功。

您也需要建立播放音訊的輸出節點。若要這樣做,請呼叫 CreateDeviceOutputNodeAsync 方法 AudioGraph 類別上,並設定 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 方法,接下來,我將討論的參考。

新增音訊效果

音訊 graph API 中有四個內建的音效 ︰ echo、 回響,等化器與 limiter。在此情況下,您會想要加入錄製聲音的迴音。加入這種效果是,只要建立 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);
}

整體回顧

已完成的 AudioEffect 類別之後,您可以從 UI 中使用它。首先,您會將按鈕新增到您的應用程式主頁面 ︰

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

您執行應用程式,然後按一下 [記錄以記錄小的剪輯。若要好消息,記錄的作業,按一下 [播放] 按鈕。要與加入迴音聽到相同的音訊,按一下 [播放與特殊效果。

總結

UWP 不僅提供豐富的支援,擷取音訊,但也有一些特殊效果套用到媒體即時的優越功能。平台包括數種可套用至音訊的效果。包括迴音器、殘響器、等化器及限制器。個別或任何數目的組合,可以套用這些效果。唯一的限制是您的想像力。


Frank La Vigne是 Microsoft 技術和民事參與小組的技術推廣者,可協助使用者充分利用技術,以便建立更好的社群。定期在他的部落格 FranksWorld.com 和已製作成 YouTube 頻道呼叫的 Frank 世界電視 (youtube.com/FranksWorldTV)。

衷心感謝以下技術專家對本文的審閱: Drew Batchelor 和 Jose Luis 方式