Skip to content

Instantly share code, notes, and snippets.

@voluntas
Last active August 7, 2019 01:47
Show Gist options
  • Save voluntas/14d0300f843d1e7dc1cea333664acd23 to your computer and use it in GitHub Desktop.
Save voluntas/14d0300f843d1e7dc1cea333664acd23 to your computer and use it in GitHub Desktop.
iOS SDK: 外部audioをWebRTC経由で流す方法の調査

iOS SDK: 外部audioをWebRTC経由で流す方法の調査

TL;DR

  • libwebrtc.framework レベルでは不可能。
  • libwebrtc.framework/modules/audio_device/ios/audio_device_ios.h の std::unique_ptr<VoiceProcessingAudioUnit> audio_unit_; を独自実装に差し替えれば可能かもしれない。

libwebrtc.frameworkの実装調査

最初に、Sora iOS SDKが使用しているlibwebrtc.frameworkの実装を見て、audio関連の実装がどうなっているかを確認した。

調査に使用したコードは2018/04/08地点での以下のリポジトリの master ブランチ。 詳細はわからないが、おそらくバージョン的にはM64相当以上? https://chromium.googlesource.com/external/webrtc

audio関連クラス (Objective-Cレイヤ)

videoと同様、まず RTCMediaStream が存在して、それが複数の audioTracks を持っている構造になっている。 audioTracksNSArray<RTCAudioTrack *> * 型で、この RTCAudioTrackRTCAudioSource という別のクラスに対して source という参照を持っている。

この RTCAudioSource が実際の音声入出力ソースになっているようで、 libwebrtc.frameworkのObjective-Cレイヤの実装からではそのボリュームだけが操作できる状態になっている。

videoはローカル側の動画源となるソース・配信元からの動画源となるストリーム・動画出力先となるレンダラの三体が完全に分離されている構造になっているのだが、 audioはどうやら見た感じローカルの音源・配信元からの音源・音声出力先の三体がすべて一つの RTCAudioSource にまとめられてしまっている構造のように見える。

またこれらのStreamに紐付けられている音声関連のクラスとは別に、 RTCAudioSessionRTCAudioSessionConfiguration いう独立した音声関連のクラスが用意されていることがわかった。 これらはiOSのAVFoundationフレームワークが提供する AVAudioSession クラスに対するラッパになっている。 AVAudioSession のインスタンスは1アプリケーションに付き1つ提供されるようになっていて、そのアプリケーション全体のオーディオ設定を管理するようになっている。 そこでlibwebrtc.frameworkはおそらくこのクラスをインターフェースとして、 AVAudioSession を初期設定したり、 ユーザーに AVAudioSession の設定を行わせたりする作りになっていると思われる。 このクラスはこのクラスで有用だが、今回の目的である任意の外部音声ソースをWebRTC経由で流す用途で直接使用できるものではない。

audio関連クラス (C++レイヤ)

RTCAudioSource は内部にC++によるコア実装である rtc::scoped_refptr<webrtc::AudioSourceInterface> nativeAudioSource を持っている。 webrtc::AudioSourceInterface/webrtc/api/mediastreaminterface.h にて定義されている。

// AudioSourceInterface is a reference counted source used for AudioTracks.
// The same source can be used by multiple AudioTracks.
class AudioSourceInterface : public MediaSourceInterface {
 public:
  class AudioObserver {
   public:
    virtual void OnSetVolume(double volume) = 0;

   protected:
    virtual ~AudioObserver() {}
  };

  // TODO(deadbeef): Makes all the interfaces pure virtual after they're
  // implemented in chromium.

  // Sets the volume of the source. |volume| is in  the range of [0, 10].
  // TODO(tommi): This method should be on the track and ideally volume should
  // be applied in the track in a way that does not affect clones of the track.
  virtual void SetVolume(double volume) {}

  // Registers/unregisters observers to the audio source.
  virtual void RegisterAudioObserver(AudioObserver* observer) {}
  virtual void UnregisterAudioObserver(AudioObserver* observer) {}

  // TODO(tommi): Make pure virtual.
  virtual void AddSink(AudioTrackSinkInterface* sink) {}
  virtual void RemoveSink(AudioTrackSinkInterface* sink) {}
};

見ての通り、音量調整と、音量が変わったときのオブザーバの登録、あとはデータシンク(ストリーム内を音声データが通ったときに呼び出されるコールバック)の登録が可能だが、 こちらから音声データを能動的に送りつけることなどはできない。

ではlibwebrtc.frameworkはどのようにしてマイクなどの音声入力デバイスを操作しているのかという疑問が残るが、 リポジトリ内のソースコードを検索したところ、以下にiOS向けの音声デバイス関連の実装クラスが発見された。

  • /modules/audio_device/ios/audio_device_ios.h
  • /modules/audio_device/ios/audio_device_ios.mm
  • /modules/audio_device/ios/voice_processing_audio_unit.h
  • /modules/audio_device/ios/voice_processing_audio_unit.mm

どうやら audio_device_ios がiOS向けの音声関連のデバイス操作実装で、 voice_processing_audio_unit がiOSの低レベルAPIを利用してマイクなどを実際に操作している箇所ということがわかった。 内部的にはAudioUnitという非常に低レベルなAPIを使用していることが明らかになった。

外部audioをWebRTCに流すことは可能か?

ここまでの調査で、現在のlibwebrtc.frameworkの実装は非常に低レベルなAPIを利用して居る上に、 以下の重大な3要素がC++の実装上で密結合になっていて切り離せないということがわかっている。

  • ローカルの音源となるソース
  • 配信元からの音源となるストリーム
  • ローカルデバイス上で音声出力を行うレンダラ

従って、普通にlibwebrtc.frameworkを使用している限りは、外部audioをWebRTCに流すのは極めて困難であると言わざるをえない。 しかしながら、 audio_device_ios 上に以下のような興味深いインターフェースが見つかった。

  // VoiceProcessingAudioUnitObserver methods.
  OSStatus OnDeliverRecordedData(AudioUnitRenderActionFlags* flags,
                                 const AudioTimeStamp* time_stamp,
                                 UInt32 bus_number,
                                 UInt32 num_frames,
                                 AudioBufferList* io_data) override;
  OSStatus OnGetPlayoutData(AudioUnitRenderActionFlags* flags,
                            const AudioTimeStamp* time_stamp,
                            UInt32 bus_number,
                            UInt32 num_frames,
                            AudioBufferList* io_data) override;

これはどうやら、マイクなどによりローカル側で発生された音声データバッファをWebRTC側に伝搬させるときのコールバックと、 逆にWebRTC側から伝搬されてきた音声データバッファをローカル側のオーディオデバイスに流す(Renderする)ためのコールバックであることがわかった。

このあたりの処理をうまく奪い取れば、マイク以外の音声データバッファをWebRTCに流し込むことは可能かもしれない。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment