- libwebrtc.framework レベルでは不可能。
- libwebrtc.framework/modules/audio_device/ios/audio_device_ios.h の
std::unique_ptr<VoiceProcessingAudioUnit> audio_unit_;
を独自実装に差し替えれば可能かもしれない。
最初に、Sora iOS SDKが使用しているlibwebrtc.frameworkの実装を見て、audio関連の実装がどうなっているかを確認した。
調査に使用したコードは2018/04/08地点での以下のリポジトリの master
ブランチ。
詳細はわからないが、おそらくバージョン的にはM64相当以上?
https://chromium.googlesource.com/external/webrtc
videoと同様、まず RTCMediaStream
が存在して、それが複数の audioTracks
を持っている構造になっている。
audioTracks
は NSArray<RTCAudioTrack *> *
型で、この RTCAudioTrack
は
RTCAudioSource
という別のクラスに対して source
という参照を持っている。
この RTCAudioSource
が実際の音声入出力ソースになっているようで、
libwebrtc.frameworkのObjective-Cレイヤの実装からではそのボリュームだけが操作できる状態になっている。
videoはローカル側の動画源となるソース・配信元からの動画源となるストリーム・動画出力先となるレンダラの三体が完全に分離されている構造になっているのだが、
audioはどうやら見た感じローカルの音源・配信元からの音源・音声出力先の三体がすべて一つの RTCAudioSource
にまとめられてしまっている構造のように見える。
またこれらのStreamに紐付けられている音声関連のクラスとは別に、
RTCAudioSession
と RTCAudioSessionConfiguration
いう独立した音声関連のクラスが用意されていることがわかった。
これらはiOSのAVFoundationフレームワークが提供する AVAudioSession
クラスに対するラッパになっている。
AVAudioSession
のインスタンスは1アプリケーションに付き1つ提供されるようになっていて、そのアプリケーション全体のオーディオ設定を管理するようになっている。
そこでlibwebrtc.frameworkはおそらくこのクラスをインターフェースとして、 AVAudioSession
を初期設定したり、
ユーザーに AVAudioSession
の設定を行わせたりする作りになっていると思われる。
このクラスはこのクラスで有用だが、今回の目的である任意の外部音声ソースをWebRTC経由で流す用途で直接使用できるものではない。
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を使用していることが明らかになった。
ここまでの調査で、現在の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に流し込むことは可能かもしれない。