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) }
何が起こったのか注意深く見てください:
- バンドル内のサウンドファイルのURLを取得し、audioFileURLの値をaudioFileに渡します
他のノードを接続する前に、プレーヤーをエンジンに接続する必要があります。これらのノードはオーディオを処理して出力します。これらのノードが生成され、オーディオエンジンがプレーヤーノードに接続されたメインミキサーノードを提供します。デフォルトでは、メインミキサーはエンジンのデフォルトの出力ノード(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の詳細
- WWDC 2014セッション502:実際のAVAudioEngine
- Appleの「オーディオの操作」
- AVFoundationでオーディオを始める:オーディオエフェクト
- iOS用オーディオチュートリアル:ファイルとデータ形式
AVAudioEngineでこのチュートリアルをお楽しみください。ご質問やご意見がございましたら、以下のディスカッションフォーラムにご参加ください。