iOSAVAudioEngineチュートリアル



Ios Avaudioengine Tutorial



翻訳:AK
免責事項:この記事を転送するには、承認のために著者に連絡してください
元の住所

このAVAudioEngineチュートリアルでは、Appleのより高度なオーディオツールキットを使用して高度なオーディオ機能を追加する方法を学習します。



ほとんどのiOS開発者にオーディオ処理について言及してください。彼らはあなたに恐れと恐れをもたらします。これは、iOS 8より前では、非常に低レベルのCoreAudioフレームワークを深く理解する必要があるためです。これを実行できる勇敢な人はごくわずかです。ありがたいことに、iOS 8とAVAudioEngineのリリースにより、これらすべてが2014年に変更されました。このAVAudioEngineチュートリアルでは、Appleの新しい高レベルオーディオツールキットを使用して、CoreAudioを詳しく調べることなくオーディオ処理アプリケーションを作成する方法を示します。

そのとおり!生のオーディオデータを収集するために、あいまいなポインタベースのC / C ++構造とメモリバッファを検索する必要がなくなりました。



このAVAudioEngineチュートリアルでは、AVAudioEngineを使用して、次の優れたポッドキャストアプリケーションを構築します。 レイキャスト 。具体的には、UIで制御されるオーディオ機能(再生/一時停止ボタン、スキップする進む/戻るボタン、プログレスバー、再生速度セレクター)を追加します。完了したら、聞くのに最適なアプリがあります 木材ジャニー
画像

開始

開始する前に、このチュートリアルの資料をダウンロードし(記事の下部にダウンロードボタンが表示されます)、Xcodeを使用してコンパイルして実行すると、基本的なインターフェイスが表示されます。

これらのコントロールは何もしませんが、ビューコントローラのIBOutletsおよび関連するIBActionにすべて接続されています。



iOSオーディオフレームワークの概要

仕事に入る前に、iOSオーディオフレームワークを簡単に見てみましょう

  • CoreAudioとAudioToolboxは、どちらも低レベルのインターフェイスフレームワークです。
  • AVFoundationはObjective-C / Swiftフレームワークです。
  • AVAudioEngineはAVFoundationの一部です。

    画像

  • AVAudioEngineは、接続されたオーディオノードのセットを定義するクラスです。プロジェクトにAVAudioPlayerNodeとAVAudioUnitTimePitchの2つのノードを追加します。

画像

音声を設定する

ViewController.swiftを開き、クラスのコンテンツを表示します。上部には、接続されているすべてのインターフェースとクラス変数が表示されます。これらのイベントはストーリーボードに関連付けられています。
次のコードをsetupAudio()に追加します。

// 1 audioFileURL = Bundle.main.url(forResource: 'Intro', withExtension: 'mp4') // 2 engine.attach(player) engine.connect(player, to: engine.mainMixerNode, format: audioFormat) engine.prepare() do { // 3 try engine.start() } catch let error { print(error.localizedDescription) }

何が起こったのか注意深く見てください:

  1. バンドル内のサウンドファイルのURLを取得し、audioFileURLの値をaudioFileに渡します
  2. 他のノードを接続する前に、プレーヤーをエンジンに接続する必要があります。これらのノードはオーディオを処理して出力します。これらのノードが生成され、オーディオエンジンがプレーヤーノードに接続されたメインミキサーノードを提供します。デフォルトでは、メインミキサーはエンジンのデフォルトの出力ノード(iOSデバイススピーカー)に接続されています。 prepare()は、必要なリソースを事前に割り当てます。

    次に、scheduleAudioFile()メソッドに次のコードを配置します

guard let audioFile = audioFile else { return } skipFrame = 0 player.scheduleFile(audioFile, at: nil) { [weak self] in self?.needsFileScheduled = true }

これにより、audioFile全体が再生されます。 at:オーディオを再生する指定時間(AVAudioTime)です。すぐに再生を開始するには、nilに設定します。ファイルは1回だけ再生されます。 [再生]ボタンをもう一度クリックしても、最初からやり直すことはありません。もう一度プレイするには、スケジュールを変更する必要があります。オーディオファイルを再生した後、完了コールバックでneedsFileScheduledを設定します

その他の再生方法

//Provides a buffer for preloading audio data - scheduleBuffer(AVAudioPCMBuffer, completionHandler: AVAudioNodeCompletionHandler? = nil): //This is like scheduleFile, you can specify the audio frame to start playing and the number of frames to play - scheduleSegment(AVAudioFile, startingFrame: AVAudioFramePosition, frameCount: AVAudioFrameCount, at: AVAudioTime?, completionHandler: AVAudioNodeCompletionHandler? = nil):

次のコードをplayTappedメソッドに追加します

// 1 sender.isSelected = !sender.isSelected // 2 if player.isPlaying { player.pause() } else { if needsFileScheduled { needsFileScheduled = false scheduleAudioFile() } player.play() }

マーク
-1ボタンの選択状態を切り替えると、ストーリーボードに設定されているボタンの画像が変更されます
-2 player.isPlayingを使用して、プレーヤーの現在のステータスを確認します。正常な場合は、一時停止できます。再生がない場合は、needsFileScheduledファイルとオーディオファイルを確認する必要があります

コンパイルして実行し、playPauseButtonをクリックします。 Rayの曲が何であるかを聞くことができるはずですが、UIフィードバックはありません。ファイルの長さがわかりません。今、それは遊んでいます。

進行状況のコールバックを追加

次のコードをviewDidLoad()メソッドに追加します

updater = CADisplayLink(target: self, selector: #selector(updateUI)) updater?.add(to: .current, forMode: .defaultRunLoopMode) updater?.isPaused = true

CADisplayLinkは、ディスプレイのリフレッシュレートと同期するタイマーオブジェクトです。セレクターupdateUIを使用してインスタンス化します。次に、それを実行ループ(この場合はデフォルトの実行ループ)に追加します。最後に、実行を開始する必要はありません。isPausedをtrueに設定するだけです。

playTapped(_ :)メソッドの実装を変更します

sender.isSelected = !sender.isSelected if player.isPlaying { disconnectVolumeTap() updater?.isPaused = true player.pause() } else { if needsFileScheduled { needsFileScheduled = false scheduleAudioFile() } connectVolumeTap() updater?.isPaused = false player.play() }

ここで重要なのは、updater.isPaused = trueを使用してUIを一時停止することです。以下のVUメーターセクションでconnectVolumeTap()とdisconnectVolumeTap()について学習します

次のコードを使用して、varcurrentFrameをオーバーライドします。AVAudioFramePosition= 0

var currentFrame: AVAudioFramePosition { // 1 guard let lastRenderTime = player.lastRenderTime, // 2 let playerTime = player.playerTime(forNodeTime: lastRenderTime) else { return 0 } // 3 return playerTime.sampleTime }

currentFrameは、プレーヤーから返される最新のオーディオデータです。詳しく見てみましょう。

1、player.lastRenderTimeはエンジンの開始時刻を返します。再生されない場合、lastRenderTimeはNILを返します。
2、player.playerTime(forNodeTime :)プレーヤーが再生しない場合、lastRenderTimeをプレーヤーの開始時間に変換しますplayerTimeはnilを返します
3、sampleTimeはオーディオファイルデータのタイムスタンプです

以下のUIを更新します。updateUI()メソッドに次のコードを入力します

// 1 currentPosition = currentFrame + skipFrame currentPosition = max(currentPosition, 0) currentPosition = min(currentPosition, audioLengthSamples) // 2 progressBar.progress = Float(currentPosition) / Float(audioLengthSamples) let time = Float(currentPosition) / audioSampleRate countUpLabel.text = formatted(time: time) countDownLabel.text = formatted(time: audioLengthSeconds - time) // 3 if currentPosition >= audioLengthSamples { player.stop() updater?.isPaused = true playPauseButton.isSelected = false disconnectVolumeTap() }

ステップバイステップで確認してみましょう
1.属性skipFrameは、currentFrameに加算または減算されるオフセットであり、最初はゼロに設定されています。 currentPositionがファイル範囲を超えないことを確認してください
2.progressBar.progressをaudioFileのcurrentPositionに更新します。 currentPositionをaudioFileのsampleRateで割って時間を計算します。 countUpLabelとcountDownLabelのテキストをaudioFileの現在の時刻に更新します

3. currentPositionがファイルの終わりに達した場合、次のようになります。

  • プレーヤーを停止します
  • 一時停止タイマー
  • playPauseButtonの選択した状態をリセットします
  • ボリュームオフイベント

    コンパイルして実行し、playPauseButtonをもう一度クリックすると、Rayのイントロが聞こえ、プログレスバーの時間情報が表示されます。

VUメーターを実装する

次に、VUメーター機能を追加します。これは、2つの列の間に配置されたUIViewです。ビューの高さは、オーディオ再生の平均パワーによって決まります。これは、オーディオ処理を行う最初のチャンスです。
1kオーディオサンプルバッファの平均パワーを計算します。オーディオサンプルバッファの平均パワーを決定する一般的な方法は、サンプルの二乗平均平方根(RMS)を計算することです。
平均パワーは、デシベルで表された一連のオーディオサンプルデータの平均値です。一連のサンプルデータの最大値であるピークパワーもあります

connectVolumeTapメソッドの下にツールメソッドを追加します

func scaledPower(power: Float) -> Float { // 1 guard power.isFinite else { return 0.0 } // 2 if power return 0.0 } else if power >= 1.0 { return 1.0 } else { // 3 return (fabs(minDb) - fabs(power)) / fabs(minDb) } }

scaledPower(power :)は、負の電力デシベル値を正の値に変換して、上記のvolumeMeterHeight.constant値を調整します。これはそれがすることです

  • 1、power.isFiniteは、電力が有効な値(つまり、NaNではない)であることを確認します。無効な場合は0.0を返します。
  • 2.ここで、vuMeterのダイナミックレンジは80dbに設定されています。 -80.0未満の値の場合、0.0が返されます。 iOSのデシベル値の範囲は-160dbで、サイレントに近く、0db、最大電力です。 minDbは-80.0に設定され、ダイナミックレンジは80dbです。この値を変更して、vuMeterにどのように影響するかを確認できます
  • 3.0.0から1.0の間で完全にスケーリングされます。

次のコードをconnectVolumeTap()メソッドに追加します

// 1 let format = engine.mainMixerNode.outputFormat(forBus: 0) // 2 engine.mainMixerNode.installTap(onBus: 0, bufferSize: 1024, format: format) { buffer, when in // 3 guard let channelData = buffer.floatChannelData, let updater = self.updater else { return } let channelDataValue = channelData.pointee // 4 let channelDataValueArray = stride(from: 0, to: Int(buffer.frameLength), by: buffer.stride).map{ channelDataValue[$0] } // 5 let rms = sqrt(channelDataValueArray.map{ $0 * $0 }.reduce(0, +) / Float(buffer.frameLength)) // 6 let avgPower = 20 * log10(rms) // 7 let meterLevel = self.scaledPower(power: avgPower) DispatchQueue.main.async { self.volumeMeterHeight.constant = !updater.isPaused ? CGFloat(min((meterLevel * self.pauseImageHeight), self.pauseImageHeight)) : 0.0 } }

ここにはたくさんの作業があるので、段階的に確認してみましょう

  • 1.mainMixerNodeのデータ形式を取得します
  • 2、installTap(onBus:0、bufferSize:1024、format:format)を使用すると、mainMixerNode出力バス上のオーディオデータにアクセスできます。 1024バイトのバッファサイズを要求しますが、要求したバッファが小さすぎるか大きすぎる場合は特に、要求されたサイズは保証されません。 Appleのドキュメントには、これらの制限が何であるかは記載されていません。完了ブロックは、AVAudioPCMBufferとAVAudioTimeをパラメーターとして受け取ります。 buffer.frameLengthをチェックして、実際のバッファーサイズを判別できます。
  • 3、buffer.floatChannelDataは、各サンプルデータへのポインターの配列を提供します。 channelDataValueは、UnsafeMutablePointerの配列です。
  • 4. UnsafeMutablePointer配列からFloat配列に変換すると、将来の計算が簡単になります。これを行うには、stride(from:to:by :)を使用して、channelDataValueにインデックス付き配列を作成します。次に、{channelDataValue [$ 0]}をマップして、データ値にアクセスしてchannelDataValueArrayに格納します
  • 5. RMSの計算には、マッピング/削減/除算の操作が含まれます。まず、マッピング操作は配列内のすべての値を二乗し、reduce操作はそれらを合計します。二乗和をバッファサイズで除算し、平方根をとってバッファ内のオーディオサンプルデータのRMSを生成します。これは0.0から1.0の間の値である必要がありますが、負の値になる場合があります。
  • 6. RMSをデシベルに変換します( 音響デシベルリファレンス )。これは-160から0の間の値である必要がありますが、rmsが負の場合、値はNaNです。

最後に、disconnectVolumeTap()に次のコードを追加します。

engine.mainMixerNode.removeTap(onBus: 0) volumeMeterHeight.constant = 0

AVAudioEngineは、バスごとに1回のクリックのみを許可します。使用しないときは削除することをお勧めします

コンパイルして実行し、playPauseButtonをクリックします。 vuMeterがアクティブになり、オーディオデータの平均パワーフィードバックを提供します。

早送り

スキップする進むボタンと戻るボタンを実装する時が来ました。 skipForwardButtonはオーディオファイルで10秒前にジャンプし、skipBackwardButtonは10秒後ろにジャンプします

seek(to :)にコードを追加します。

guard let audioFile = audioFile, let updater = updater else { return } // 1 skipFrame = currentPosition + AVAudioFramePosition(time * audioSampleRate) skipFrame = max(skipFrame, 0) skipFrame = min(skipFrame, audioLengthSamples) currentPosition = skipFrame // 2 player.stop() if currentPosition false // 3 player.scheduleSegment(audioFile, startingFrame: skipFrame, frameCount: AVAudioFrameCount(audioLengthSamples - skipFrame), at: nil) { [weak self] in self?.needsFileScheduled = true } // 4 if !updater.isPaused { player.play() } }

ステップバイステップの詳細

  • 1. audioSampleRateを掛けて、時間(秒単位)をフレーム位置に変換し、currentPositionに追加します。次に、skipFrameがファイルの先頭の前になく、ファイルの末尾を超えていないことを確認します。
  • 2、player.stop()は再生を停止するだけでなく、以前にスケジュールされたすべてのイベントをクリアします。 updateUI()を呼び出して、UIを新しいcurrentPosition値に設定します
  • 3. player.scheduleSegment(_:startingFrame:frameCount:at :)は、audioFileのskipFrame位置から再生を開始するように調整します。 frameCountは、再生されるフレームの数です。ファイルの最後まで再生したいので、audioLengthSamples-skipFrameに設定します。最後に、at:nilは、将来のある時点で再生を開始するのではなく、再生をすぐに開始する必要があることを指定します。
  • 4.スキップを呼び出す前にプレーヤーが再生している場合は、player.play()を呼び出して再生を再開します。 updater.isPausedを使用すると、プレーヤーが以前に一時停止された場合にのみ有効になるため、これを簡単に判断できます。

ビルドして実行し、playPauseButtonをクリックします。 skipBackwardButtonをクリックし、skipForwardButtonを使用して前後にスキップします。 progressBarを観察し、変更をカウントします

コードレートの変更を実現する

最後に達成することは、再生速度を変更することです。今日では、ポッドキャストを2倍以上の速度で聞くことが人気の機能です

setupAudio()メソッドを次のコードに置き換えます

engine.attach(player) engine.attach(rateEffect) engine.connect(player, to: rateEffect, format: audioFormat) engine.connect(rateEffect, to: engine.mainMixerNode, format: audioFormat)

これにより、rateEffect(AVAudioUnitTimePitchノード)がオーディオグラフに接続され、接続されます。このノードタイプはエフェクトノードです。具体的には、再生速度とオーディオピッチを変更できます。
didChangeRateValue()アクションは、rateSliderへの変更を処理します。これは、rateSliderValues配列のインデックスを計算し、rateEffect.rateを設定するrateValueを設定します。 rateSliderの値の範囲は0.5xから3.0xです

ビルドして実行し、playPauseButtonをクリックします。レイの新しい効果を聞くためにrateSliderを調整します

次は何?
このチュートリアルのダウンロードリンクの上部と下部にあるプロジェクトコードをダウンロードしてダウンロードできます。
他の効果を表示するaudioSetup()メソッドに追加できます。1つの方法は、スライダーを使用してrateEffect.pitchを制御することです。
AVAudioEngineが表示できるiOSの詳細

AVAudioEngineでこのチュートリアルをお楽しみください。ご質問やご意見がございましたら、以下のディスカッションフォーラムにご参加ください。