ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
音声処理の新機能
Appleの音声処理APIを使用して、VoIPアプリでベストなオーディオ体験を実現する方法を紹介します。ミュート中に誰かが話していることを検出する方法や、他のオーディオのダッキング動作を調整する方法などを説明します。
関連する章
- 0:00 - Introduction
- 3:19 - Other audio ducking
- 7:55 - Muted talker detection
- 11:37 - Muted talker detection for macOS
リソース
関連ビデオ
WWDC23
-
ダウンロード
♪ ♪
こんにちは 「音声処理の新機能」へようこそ Core Audioチームの Julianです ボイスオーバーIPアプリケーションは 同僚や友人 家族との つながりを維持するために これまで以上に不可欠です 音声チャットの音質は優れたユーザー体験を 提供する上で 重要な役割を果たします どのような状況でも 素晴らしいサウンドが得られる― オーディオ処理の実装は重要ですが 難しいことでもあります だからこそAppleが 音声処理APIを提供します あなたのアプリでチャットする際に 音響環境や 使用するApple製品や 接続されるオーディオアクセサリに関わらず 誰もが常に最高のオーディオ体験を 楽しむことができます Appleの音声処理APIは FaceTimeや 電話アプリを含む 多くのアプリによって 広く使用されています また クラス最高のオーディオ 信号処理を提供します エコーキャンセレーションやノイズ抑制 自動ゲインコントロールなどにより ボイスチャットの音声を向上させるのです その性能は 音響エンジニアによって Apple製品の各モデルと 各種オーディオ機器との組み合わせで それぞれの固有の音響特性を 考慮して調整されています Appleの音声処理APIを選択すると 標準版 音声分離 ワイドスペクトルなど アプリのマイクモード設定を ユーザーがフルに管理できます Voice over IPアプリには Apple音声処理APIの使用を 強くお勧めします アップルの音声処理APIには 2つの選択肢があります まずAUVoiceProcessingIOと呼ばれる I/Oオーディオユニットがあります これはI/Oオーディオユニットと 直接やり取りするアプリ向けです
2つ目はAVAudioEngineで 具体的にはAVAudioEngineの 「音声処理」モードを有効にします
AVAudioEngineは上位APIです 一般的には使いやすく オーディオを扱う際に書くべきコード量を 軽減できます どちらのオプションも 同じ音声処理機能を提供します さて 新しい点は何かというと 音声処理APIを初めて tvOSで利用できるようになりました 詳細は「Discover Continuity Camera for tvOS」を ご覧ください また AUVoiceIOとAVAudioEngineに 新しいAPIをいくつか追加し 音声処理をより制御可能にし 新機能の実装をサポートします
最初のAPIは他のオーディオの ダッキング動作をコントロールするものです 何を意味するかは後ほど説明します 2つ目のAPIはミュート通話者の 検出機能をアプリに 実装するサポートをします このセッションでは これら2つの 新しいAPIの詳細に焦点を当てます 最初にお話しするAPIは 「Other audio ducking」です このAPIに取り組む前に 他のオーディオとは何か および ダッキングが重要な理由を説明します Appleの音声処理APIを使用する場合 再生音声に何が起こっているのか 見てみましょう あなたのアプリは Appleの音声処理で処理され 出力デバイスに再生される ボイスチャットオーディオストリームを 提供しています しかし 同時に他のオーディオストリームが 再生されている可能性があります 例えば あなたのアプリは 音声処理APIを通して レンダリングされていない 別のオーディオストリームを再生できます
同時に他のアプリでもオーディオを 再生している可能性もあります あなたのアプリからの 音声オーディオストリーム以外の すべてのオーディオストリームは Appleの音声処理によって 「他のオーディオ」とみなされ あなたの音声オーディオは 再生前に出力デバイスに 他のオーディオと混合されます ボイスチャットアプリの場合は 通常 再生オーディオの主な焦点は ボイスチャットオーディオです よって音声の明瞭度を向上させるために 他の音声の音量レベルを下げています 以前は 他のオーディオに 一定のダッキングを適用していました これはほとんどのアプリで うまく機能しており あなたのアプリの 現在のダッキング動作が 十分に満足できるものであれば 何もする必要はありません しかし アプリによっては ダッキングの動作を さらに制御したい場合もあるでしょう
まずはAUVoiceIOのAPIを検証し AVAudioEngineについては あとで説明します AUVoiceIOの場合 他の オーディオダッキング設定の 構造は次の通りです これはダッキングの2つの独立した側面の コントロールを提供します ダッキングのスタイルである mEnableAdvancedDuckingと ダッキングの量であるmDuckingLevelです mEnableAdvancedDuckingについては デフォルトでは無効になっています 有効化されるとチャット参加者の どちらか一方からの 音声アクティビティの存在に基づいて ダッキングレベルが動的に調整されます つまり 一方のユーザーが話しているときは より多くのダッキングを適用し どちらも話していないときは ダッキングを減らします これはFaceTime SharePlayの ダッキングと似ており FaceTimeの両者が話していないときは メディア再生の音量が大きく 誰かが話し始めるとすぐに メディア再生の音量が小さくなります
次にmDuckingLevelについて コントロールには4つのレベルがあります デフォルト(Default) 最小(Min) 中程度(Mid) 最大(Max)です デフォルト(Default)の ダッキングレベルはこれまでと同じ― ダッキング量を適用し これは今後も デフォルトの設定となります 最小(Min)レベルでは 適用する ダッキング量を最小限に抑えます つまり 他のオーディオの音量を できるだけ大きくしたい場合に 使用する設定です 逆に 最大(Max)ダッキングレベルは ダッキング量を最大にします 一般的に 高いダッキングレベルを 選択すると ボイスチャットの明瞭度が向上します
2つのコントロールは独立して使用できます 併用することでダッキングの挙動を 自在にコントロールできます
ダッキング設定について説明したので 次はあなたのアプリに適した 設定を作成します 例えば ここでは 高度なダッキングを有効にし ダッキングレベルを最小に選択します
次に、kAUVoiceIOProperty_ OtherAudioDuckingConfigurationを介して ダッキング設定を AUVoiceIOに設定します
AVAudioEngineクライアントの場合 APIは非常によく似ています 以下は その他のオーディオ ダッキング設定の構造体定義で これはダッキングレベルの列挙型定義です
AVAudioEngineで このAPIを使用するには まずエンジンの入力ノードで 音声処理を有効にしてから
ダッキング設定を行います
そして最後に 入力ノードに設定を入力します アプリに非常に便利な機能を 実装する際に役立つ― もう一つのAPIについて説明します オンライン会議で 同僚や友人と チャットしているつもりが しばらくしてからミュートに気づき あなたの素晴らしい指摘や面白い話を 誰も聞いていないことに 気づいたという経験はありませんか? 気まずいですよね FaceTimeがここで行っているように ミュートされた話し手を検出する機能が アプリにあると非常に便利です
そのため ミュートされた通話者の存在を 検出するためのAPIを提供しています iOS 15で初めて導入され 現在はmacOS 14およびtvOS 17で 利用可能です このAPIの使い方の概略をお伝えします まず AUVoiceIOまたは AVAudioEnginにリスナーブロックを 提供し ミュートされた通話者の検出時に 通知を受け取る必要があります ミュートされた通話者が話し始めたり 話をやめたりするたびに 提供されたリスナーブロックが 呼び出されます そのような通知に対する 処理コードを実装してください 例えば ミュート中にユーザーが 話し始めたことを通知した場合 ミュートを解除するよう促すことができます 最後に AUVoiceIOまたは AVAudioEngineの ミュートAPIを使ってミュートを実装する ことが要件になっています
AUVoiceIOを使ったコード例を いくつか紹介します AVAudioEngineの例については 後ほど説明します まず 通知を処理する リスナーブロックを用意します
このブロックにはAUVoiceIOSpeech ActivityEvent型のパラメータがあり SpeechActivityHasStartedまたは SpeechActivityHasEndedの 2つの値のうち いずれかを指定します
このリスナーブロックはミュート中 スピーチアクティビティイベントが 変化するたびに呼び出されます
ブロックの中ではこのイベントを どのように処理するかを実装します 例えば SpeechActivityHasStarted イベントを受信したとき ユーザーにミュートを解除するよう 促すことができます リスナーブロックの準備ができたら kAUVoiceIOProperty_MutedSpeech ActivityEventListenerを 介して AUVoiceIOにブロックを登録します
ユーザーがミュートした場合 ミュートAPIの kAUVoiceIOProperty_MuteOutputを 使用してミュートを実装します
リスナーブロックが呼び出されるのは A)ユーザーがミュートされた時 B)スピーチアクティビティの状態が 変化したときだけです
発話があってもなくても 冗長な通知は発生しません
AVAudioEngineクライアントの実装も これに非常に似ています エンジンの入力ノードで 音声処理を有効にしたら 通知を処理する リスナーブロックを用意します
次に リスナーブロックを 入力ノードに登録します
ミュートした場合は AVAudioEngineの 音声処理ミュートAPIでミュートにします
AUVoiceIOと AVAudioEngineを使った ミュート通話者の検出機能の 実装についてお伝えしました Appleの音声処理APIを 採用する準備がまだの方のために この機能を実装するための 代替手段を提供します
この代替オプションは CoreAudio HAL API つまりハードウェア抽象化レイヤーAPIを 介してのみmacOSで利用可能です 2つの新しいHALプロパティがあり 組み合わせて使用することで 音声アクティビティを 検出する際に役立ちます まず kAudioDevicePropertyVoice ActivityDetectionEnableによって 入力デバイスの 音声アクティビティ検出を有効にします 次に HALプロパティリスナーを kAudioDevicePropertyVoice ActivityDetectionStateに登録します このHALプロパティリスナーは 音声アクティビティの状態に 変更があるたびに呼び出されます アプリが プロパティリスナーから通知を受けたら プロパティにクエリを実行して 現在の値を取得します
コードの例を挙げて説明しましょう
入力デバイスで音声アクティビティ検出を 有効にするには まず HALプロパティアドレスを構築します
次に このプロパティを 入力デバイスに設定して有効にします
次に 音声アクティビティ検出状態 プロパティにリスナーを登録するには HALプロパティアドレスを作成し プロパティリスナーを指定します
ここでの「listener_callback」は リスナー関数の名前です
これがプロパティリスナーの 実装方法の例です
リスナーはこの関数シグネチャに従います
この例では リスナーが 1つのHALプロパティに対してのみ 登録されていると仮定します つまり このリスナーが呼び出されたとき どのHALプロパティが変更されたのか 曖昧になることはありません
複数のHALプロパティの通知用に 同じリスナーを登録する場合 最初にinAddressesの配列を調べて 何が変更されたかを 正確に確認する必要があります
この通知を処理するには VoiceActivityDetectionState プロパティに問い合わせて
現在の値を取得し その値を処理する 独自のロジックを実装します
音声アクティビティ検出HAL APIには 何点か重要な詳細があります まず エコーキャンセルされた マイク入力から 音声アクティビティを検出するので ボイスチャットアプリに最適です
第二に この検出はプロセスの ミュート状態に関係なく機能します この機能を使ってミュート通話者の 検出機能を実装するには 音声アクティビティ状態と ミュート状態を組み合わせる― 追加ロジックをアプリに 実装する必要があります HAL APIクライアントが ミュートを実装するには HALのプロセスミュートAPIの使用を 強くお勧めします メニューバーの 録画インジケーターランプを抑制し ユーザーはミュート状態でのプライバシーが 保護されていることに安心できます 今日の話を振り返りましょう Appleの音声処理APIについて紹介し これをボイスオーバーIPアプリに おすすめする理由をお伝えしました AUVoiceIOと AVAudioEngineを使った― ダッキング動作のコード例を交えながら 他の音声のダッキングやダッキング動作を 制御するAPIについてお話しました また AUVoiceIOと AVAudioEngineのコード例を用いて ミュート通話者の検出の 実装方法についても説明しました またAppleの音声処理APIを 採用していないクライアントのために Core Audio HAL APIを使って macOS上で音声処理を行う 代替オプションも紹介しました Appleの音声処理APIを使用した― 素晴らしいアプリを皆さんが 制作されるのを楽しみにしています ご視聴ありがとうございました! ♪ ♪
-
-
5:50 - Other audio ducking
// Insert code snipp297struct AUVoiceIOOtherAudioDuckingConfiguration { Boolean mEnableAdvancedDucking; AUVoiceIOOtherAudioDuckingLevel mDuckingLevel; };et. typedef CF_ENUM(UInt32, AUVoiceIOOtherAudioDuckingLevel) { kAUVoiceIOOtherAudioDuckingLevelDefault = 0, kAUVoiceIOOtherAudioDuckingLevelMin = 10, kAUVoiceIOOtherAudioDuckingLevelMid = 20, kAUVoiceIOOtherAudioDuckingLevelMax = 30 };
-
6:48 - Other audio ducking
const AUVoiceIOOtherAudioDuckingConfiguration duckingConfig = { .mEnableAdvancedDucking = true, .mDuckingLevel = AUVoiceIOOtherAudioDuckingLevel::kAUVoiceIOOtherAudioDuckingLevelMin }; // AUVoiceIO creation code omitted OSStatus err = AudioUnitSetProperty(auVoiceIO, kAUVoiceIOProperty_OtherAudioDuckingConfiguration, kAudioUnitScope_Global, 0, &duckingConfig, sizeof(duckingConfig));
-
6:50 - Other audio ducking
const AUVoiceIOOtherAudioDuckingConfiguration duckingConfig = { .mEnableAdvancedDucking = true, .mDuckingLevel = AUVoiceIOOtherAudioDuckingLevel::kAUVoiceIOOtherAudioDuckingLevelMin }; // AUVoiceIO creation code omitted OSStatus err = AudioUnitSetProperty(auVoiceIO, kAUVoiceIOProperty_OtherAudioDuckingConfiguration, kAudioUnitScope_Global, 0, &duckingConfig, sizeof(duckingConfig));
-
7:20 - Other audio ducking
public struct AVAudioVoiceProcessingOtherAudioDuckingConfiguration { public var enableAdvancedDucking: ObjCBool public var duckingLevel: AVAudioVoiceProcessingOtherAudioDuckingConfiguration.Level } extension AVAudioVoiceProcessingOtherAudioDuckingConfiguration { public enum Level : Int, @unchecked Sendable { case `default` = 0 case min = 10 case mid = 20 case max = 30 } }
-
7:31 - Other audio ducking
let engine = AVAudioEngine() let inputNode = engine.inputNode do { try inputNode.setVoiceProcessingEnabled(true) } catch { print("Could not enable voice processing \(error)") } let duckingConfig = AVAudioVoiceProcessingOtherAudioDuckingConfiguration(mEnableAdvancedDucking: false, mDuckingLevel: .max) inputNode.voiceProcessingOtherAudioDuckingConfiguration = duckingConfig
-
7:32 - Muted talker detection AUVoiceIO
AUVoiceIOMutedSpeechActivityEventListener listener = ^(AUVoiceIOMutedSpeechActivityEvent event) { if (event == kAUVoiceIOSpeechActivityHasStarted) { // User has started talking while muted. Prompt the user to un-mute } else if (event == kAUVoiceIOSpeechActivityHasEnded) { // User has stopped talking while muted } }; OSStatus err = AudioUnitSetProperty(auVoiceIO, kAUVoiceIOProperty_MutedSpeechActivityEventListener, kAudioUnitScope_Global, 0, &listener, sizeof(AUVoiceIOMutedSpeechActivityEventListener)); // When user mutes UInt32 muteUplinkOutput = 1; result = AudioUnitSetProperty(auVoiceIO, kAUVoiceIOProperty_MuteOutput, kAudioUnitScope_Global, 0, &muteUplinkOutput, sizeof(muteUplinkOutput));
-
11:08 - Muted talker detection AVAudioEngine
let listener = { (event : AVAudioVoiceProcessingSpeechActivityEvent) in if (event == AVAudioVoiceProcessingSpeechActivityEvent.started) { // User has started talking while muted. Prompt the user to un-mute } else if (event == AVAudioVoiceProcessingSpeechActivityEvent.ended) { // User has stopped talking while muted } } inputNode.setMutedSpeechActivityEventListener(listener) // When user mutes inputNode.isVoiceProcessingInputMuted = true
-
12:31 - Voice activity detection - implementation with HAL APIs
// Enable Voice Activity Detection on the input device const AudioObjectPropertyAddress kVoiceActivityDetectionEnable{ kAudioDevicePropertyVoiceActivityDetectionEnable, kAudioDevicePropertyScopeInput, kAudioObjectPropertyElementMain }; OSStatus status = kAudioHardwareNoError; UInt32 shouldEnable = 1; status = AudioObjectSetPropertyData(deviceID, &kVoiceActivityDetectionEnable, 0, NULL, sizeof(UInt32), &shouldEnable); // Register a listener on the Voice Activity Detection State property const AudioObjectPropertyAddress kVoiceActivityDetectionState{ kAudioDevicePropertyVoiceActivityDetectionState, kAudioDevicePropertyScopeInput, kAudioObjectPropertyElementMain }; status = AudioObjectAddPropertyListener(deviceID, &kVoiceActivityDetectionState, (AudioObjectPropertyListenerProc)listener_callback, NULL); // “listener_callback” is the name of your listener function
-
13:13 - Voice activity detection - listener_callback implementation
OSStatus listener_callback( AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress* __nullable inAddresses, void* __nullable inClientData) { // Assuming this is the only property we are listening for, therefore no need to go through inAddresses UInt32 voiceDetected = 0; UInt32 propertySize = sizeof(UInt32); OSStatus status = AudioObjectGetPropertyData(inObjectID, &kVoiceActivityState, 0, NULL, &propertySize, &voiceDetected); if (kAudioHardwareNoError == status) { if (voiceDetected == 1) { // voice activity detected } else if (voiceDetected == 0) { // voice activity not detected } } return status; };
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。