From 25b1d68ee08b3a049ad6c1b886d3bf8946172552 Mon Sep 17 00:00:00 2001 From: tnoho Date: Tue, 11 Jul 2023 00:56:51 +0900 Subject: [PATCH 01/33] =?UTF-8?q?=E3=83=93=E3=83=87=E3=82=AA=E5=91=A8?= =?UTF-8?q?=E3=82=8A=E3=81=AE=E9=96=A2=E6=95=B0=E3=81=AB=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora.h | 36 ++++++++++++++++++++++++++++++++++++ src/sora_video_sink.h | 28 +++++++++++++++++++++++++++- src/sora_video_source.h | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/src/sora.h b/src/sora.h index 23cd2765..9c6bf23d 100644 --- a/src/sora.h +++ b/src/sora.h @@ -12,12 +12,28 @@ #include "sora_track_interface.h" #include "sora_video_source.h" +/** + * Sora Python SDK のベースになるクラスです。 + * + * SoraFactory を内包し Connection や AudioSource、VideoSource を生成します。 + * 一つの Sora インスタンスから複数の Connection、AudioSource、VideoSource が生成できます。 + * 同じ Sora インスタンス内でしか Connection や AudioSource、VideoSource を共有できないので、 + * 複数の Sora インスタンスを生成することは不具合の原因になります。 + */ class Sora : public DisposePublisher { public: Sora(std::optional use_hardware_encoder, std::optional openh264); ~Sora(); + /** + * Sora と接続する Connection を生成します。 + * + * 実装上の留意点:Sora C++ SDK が observer に std::weak_ptr を要求するためポインタで返す Source とは異なり、 + * std::shared_ptr で返しますが Python での扱いは変わりません。 + * + * @return SoraConnection インスタンス + */ std::shared_ptr CreateConnection( // 必須パラメータ const nb::handle& signaling_urls, @@ -67,7 +83,27 @@ class Sora : public DisposePublisher { std::optional proxy_password, std::optional proxy_agent); + /** + * Sora に音声データを送る受け口である SoraAudioSource を生成します。 + * + * AudioSource に音声データを渡すことで、 Sora に音声を送ることができます。 + * AudioSource は MediaStreamTrack として振る舞うため、 + * AudioSource と同一の Sora インスタンスから生成された複数の Connection で共用できます。 + * + * @param channels AudioSource に入力する音声データのチャネル数 + * @param sample_rate AudioSource に入力する音声データのサンプリングレート + * @return SoraAudioSource インスタンス + */ SoraAudioSource* CreateAudioSource(size_t channels, int sample_rate); + /** + * Sora に映像データを送る受け口である SoraVideoSource を生成します。 + * + * VideoSource にフレームデータを渡すことで、 Sora に映像を送ることができます。 + * VideoSource は MediaStreamTrack として振る舞うため、 + * VideoSource と同一の Sora インスタンスから生成された複数の Connection で共用できます。 + * + * @return SoraVideoSource インスタンス + */ SoraVideoSource* CreateVideoSource(); private: diff --git a/src/sora_video_sink.h b/src/sora_video_sink.h index 6af6587e..f95ae986 100644 --- a/src/sora_video_sink.h +++ b/src/sora_video_sink.h @@ -18,10 +18,21 @@ namespace nb = nanobind; +/** + * Sora からのフレームを格納する SoraVideoFrame です。 + * + * on_frame_ コールバックで直接フレームデータの ndarray を返してしまうとメモリーリークしてしまうため、 + * フレームデータを Python で適切にハンドリングできるようにするために用意しました。 + */ class SoraVideoFrame { public: SoraVideoFrame(rtc::scoped_refptr i420_data); + /** + * SoraVideoFrame 内のフレームデータへの numpy.ndarray での参照を渡します。 + * + * @return NumPy の配列 numpy.ndarray で W x H x BGR になっているフレームデータ + */ nb::ndarray> Data(); private: @@ -30,9 +41,17 @@ class SoraVideoFrame { std::unique_ptr argb_data_; }; +/** + * Sora からの映像を受け取る SoraVideoSinkImpl です。 + * + * Connection の OnTrack コールバックから渡されるリモート Track から映像を取り出すことができます。 + */ class SoraVideoSinkImpl : public rtc::VideoSinkInterface, public DisposeSubscriber { public: + /** + * @param track 映像を取り出す OnTrack コールバックから渡されるリモート Track + */ SoraVideoSinkImpl(SoraTrackInterface* track); ~SoraVideoSinkImpl(); @@ -45,7 +64,14 @@ class SoraVideoSinkImpl : public rtc::VideoSinkInterface, // DisposeSubscriber void PublisherDisposed() override; - // このコールバックは shared_ptr にしないとリークする + /** + * フレームデータが来るたびに呼び出されるコールバック変数です。 + * + * フレームが受信される度に呼び出されます。 + * このコールバック関数内では重い処理は行わないでください。サンプルを参考に queue を利用するなどの対応を推奨します。 + * また、この関数はメインスレッドから呼び出されないため、関数内で cv2.imshow を実行しても macOS の場合は表示されません。 + * 実装上の留意点:このコールバックで渡す引数は shared_ptr にしておかないとリークします + */ std::function)> on_frame_; private: diff --git a/src/sora_video_source.h b/src/sora_video_source.h index 624089ba..9b587916 100644 --- a/src/sora_video_source.h +++ b/src/sora_video_source.h @@ -22,6 +22,14 @@ namespace nb = nanobind; +/** + * Sora に映像データを送る受け口である SoraVideoSource です。 + * + * VideoSource にフレームデータを渡すことで、 Sora に映像を送ることができます。 + * 送信時通信状況によってはフレームのリサイズやドロップが行われます。 + * VideoSource は MediaStreamTrack として振る舞うため、 + * VideoSource と同一の Sora インスタンスから生成された複数の Connection で共用できます。 + */ class SoraVideoSource : public SoraTrackInterface { public: SoraVideoSource(DisposePublisher* publisher, @@ -30,15 +38,45 @@ class SoraVideoSource : public SoraTrackInterface { void Disposed() override; void PublisherDisposed() override; + /** + * Sora に映像データとして送るフレームを渡します。 + * + * この関数が呼び出された時点のタイムスタンプでフレームを送信します。 + * 映像になるように一定のタイミングで呼び出さない場合、受信側でコマ送りになります。 + * + * @param ndarray NumPy の配列 numpy.ndarray で W x H x BGR になっているフレームデータ + */ void OnCaptured(nb::ndarray, nb::c_contig, nb::device::cpu> ndarray); + /** + * Sora に映像データとして送るフレームを渡します。 + * + * timestamp 引数で渡されたタイムスタンプでフレームを送信します。 + * フレームのタイムスタンプを指定できるようにするため用意したオーバーロードです。 + * timestamp が映像になるように一定の時間差がない場合、受信側で正しく表示されない場合があります。 + * 表示側で音声データの timestamp と同期を取るため遅延が発生する場合があります。 + * + * @param ndarray NumPy の配列 numpy.ndarray で W x H x BGR になっているフレームデータ + * @param timestamp Python の time.time() で取得できるエポック秒で表されるフレームのタイムスタンプ + */ void OnCaptured(nb::ndarray, nb::c_contig, nb::device::cpu> ndarray, double timestamp); + /** + * Sora に映像データとして送るフレームを渡します。 + * + * timestamp_us 引数で渡されたマイクロ秒精度の整数で表されるタイムスタンプでフレームを送信します。 + * libWebRTC のタイムスタンプはマイクロ秒精度のため用意したオーバーロードです。 + * timestamp が映像になるように一定の時間差がない場合、受信側で正しく表示されない場合があります。 + * 表示側で音声データの timestamp と同期を取るため遅延が発生する場合があります。 + * + * @param ndarray NumPy の配列 numpy.ndarray で W x H x BGR になっているフレームデータ + * @param timestamp_us マイクロ秒単位の整数で表されるフレームのタイムスタンプ + */ void OnCaptured(nb::ndarray, nb::c_contig, From 9d8cc0708aac1b738417df9b9806495975939c5e Mon Sep 17 00:00:00 2001 From: tnoho Date: Tue, 11 Jul 2023 20:43:03 +0900 Subject: [PATCH 02/33] =?UTF-8?q?=E3=83=93=E3=83=87=E3=82=AA=E3=81=AE?= =?UTF-8?q?=E5=85=A5=E5=8A=9B=E5=BD=A2=E5=BC=8F=E3=81=8C=E9=96=93=E9=81=95?= =?UTF-8?q?=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E3=81=A7=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_video_sink.h | 4 +++- src/sora_video_source.h | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sora_video_sink.h b/src/sora_video_sink.h index f95ae986..e2f5c72b 100644 --- a/src/sora_video_sink.h +++ b/src/sora_video_sink.h @@ -31,7 +31,7 @@ class SoraVideoFrame { /** * SoraVideoFrame 内のフレームデータへの numpy.ndarray での参照を渡します。 * - * @return NumPy の配列 numpy.ndarray で W x H x BGR になっているフレームデータ + * @return NumPy の配列 numpy.ndarray で H x W x BGR になっているフレームデータ */ nb::ndarray> Data(); @@ -45,6 +45,8 @@ class SoraVideoFrame { * Sora からの映像を受け取る SoraVideoSinkImpl です。 * * Connection の OnTrack コールバックから渡されるリモート Track から映像を取り出すことができます。 + * 実装上の留意点:Track の参照保持のための Impl のない SoraVideoSink を __init__.py に定義しています。 + * SoraVideoSinkImpl を直接 Python から呼び出すことは想定していません。 */ class SoraVideoSinkImpl : public rtc::VideoSinkInterface, public DisposeSubscriber { diff --git a/src/sora_video_source.h b/src/sora_video_source.h index 9b587916..bc598141 100644 --- a/src/sora_video_source.h +++ b/src/sora_video_source.h @@ -44,7 +44,7 @@ class SoraVideoSource : public SoraTrackInterface { * この関数が呼び出された時点のタイムスタンプでフレームを送信します。 * 映像になるように一定のタイミングで呼び出さない場合、受信側でコマ送りになります。 * - * @param ndarray NumPy の配列 numpy.ndarray で W x H x BGR になっているフレームデータ + * @param ndarray NumPy の配列 numpy.ndarray で H x W x BGR になっているフレームデータ */ void OnCaptured(nb::ndarray, @@ -58,7 +58,7 @@ class SoraVideoSource : public SoraTrackInterface { * timestamp が映像になるように一定の時間差がない場合、受信側で正しく表示されない場合があります。 * 表示側で音声データの timestamp と同期を取るため遅延が発生する場合があります。 * - * @param ndarray NumPy の配列 numpy.ndarray で W x H x BGR になっているフレームデータ + * @param ndarray NumPy の配列 numpy.ndarray で H x W x BGR になっているフレームデータ * @param timestamp Python の time.time() で取得できるエポック秒で表されるフレームのタイムスタンプ */ void OnCaptured(nb::ndarray Date: Tue, 11 Jul 2023 20:44:04 +0900 Subject: [PATCH 03/33] =?UTF-8?q?=E9=9F=B3=E5=A3=B0=E5=91=A8=E3=82=8A?= =?UTF-8?q?=E3=81=AE=E9=96=A2=E6=95=B0=E3=81=AB=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_sink.h | 19 +++++++++++++++++++ src/sora_audio_source.h | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/sora_audio_sink.h b/src/sora_audio_sink.h index 2b921a5c..798ca6d7 100644 --- a/src/sora_audio_sink.h +++ b/src/sora_audio_sink.h @@ -19,6 +19,14 @@ namespace nb = nanobind; +/** + * Sora からの音声を受け取る SoraAudioSinkImpl です。 + * + * Connection の OnTrack コールバックから渡されるリモート Track から音声を取り出すことができます。 + * Track からの音声は SoraAudioSinkImpl 内のバッファに溜め込まれるため、任意のタイミングで音声を取り出すことができます。 + * 実装上の留意点:Track の参照保持のための Impl のない SoraAudioSink を __init__.py に定義しています。 + * SoraAudioSinkImpl を直接 Python から呼び出すことは想定していません。 + */ class SoraAudioSinkImpl : public webrtc::AudioTrackSinkInterface, public DisposeSubscriber { public: @@ -38,11 +46,22 @@ class SoraAudioSinkImpl : public webrtc::AudioTrackSinkInterface, size_t number_of_frames, absl::optional absolute_capture_timestamp_ms) override; + /** + * 実装上の留意点:コールバックと Read 関数の共存はパフォーマンスや使い方の面で難しいことが判明したので、 + * on_data_, on_format_ ともに廃止予定です。 + */ std::function>)> on_data_; std::function on_format_; + /** + * 受信済みのデータをバッファから読み出す + * + * @param frames 受け取るチャンネルごとのサンプル数。0 を指定した場合には、受信済みのすべてのサンプルを返す + * @param timeout 溜まっているサンプル数が frames で指定した数を満たさない場合の待ち時間。秒単位の float で指定する + * @return Tuple でインデックス 0 には成否が、成功した場合のみインデックス 1 には NumPy の配列 numpy.ndarray で チャンネルごとのサンプル数 x チャンネル数 になっている音声データ + */ nb::tuple Read(size_t frames, float timeout); private: diff --git a/src/sora_audio_source.h b/src/sora_audio_source.h index a9c4bb9f..7522028a 100644 --- a/src/sora_audio_source.h +++ b/src/sora_audio_source.h @@ -18,6 +18,12 @@ namespace nb = nanobind; +/** + * SoraAudioSourceInterface は SoraAudioSource の実体です。 + * + * 実装上の留意点:webrtc::Notifier を継承しているクラスは + * nanobind で直接的な紐付けを行うとエラーが出るため SoraAudioSource とはクラスを分けました。 + */ class SoraAudioSourceInterface : public webrtc::Notifier { public: @@ -55,6 +61,13 @@ class SoraAudioSourceInterface int64_t last_timestamp_; }; +/** + * Sora に音声データを送る受け口である SoraAudioSource です。 + * + * AudioSource に音声データを渡すことで、 Sora に音声を送ることができます。 + * AudioSource は MediaStreamTrack として振る舞うため、 + * AudioSource と同一の Sora インスタンスから生成された複数の Connection で共用できます。 + */ class SoraAudioSource : public SoraTrackInterface { public: SoraAudioSource(DisposePublisher* publisher, @@ -63,15 +76,43 @@ class SoraAudioSource : public SoraTrackInterface { size_t channels, int sample_rate); + /** + * Sora に送る音声データを渡します。 + * + * @param data 送信する 16bit PCM データの参照 + * @param samples_per_channel チャンネルごとのサンプル数 + * @param timestamp Python の time.time() で取得できるエポック秒で表されるフレームのタイムスタンプ + */ void OnData(const int16_t* data, size_t samples_per_channel, double timestamp); + /** + * Sora に送る音声データを渡します。 + * + * タイムスタンプは先に受け取ったデータと連続になっていると想定してサンプル数から自動生成します。 + * + * @param data 送信する 16bit PCM データの参照 + * @param samples_per_channel チャンネルごとのサンプル数 + */ void OnData(const int16_t* data, size_t samples_per_channel); + /** + * Sora に送る音声データを渡します。 + * + * @param ndarray NumPy の配列 numpy.ndarray で チャンネルごとのサンプル数 x チャンネル数 になっている音声データ + * @param timestamp Python の time.time() で取得できるエポック秒で表されるフレームのタイムスタンプ + */ void OnData(nb::ndarray, nb::c_contig, nb::device::cpu> ndarray, double timestamp); + /** + * Sora に送る音声データを渡します。 + * + * タイムスタンプは先に受け取ったデータと連続になっていると想定してサンプル数から自動生成します。 + * + * @param ndarray NumPy の配列 numpy.ndarray で チャンネルごとのサンプル数 x チャンネル数 になっている音声データ + */ void OnData(nb::ndarray, nb::c_contig, From 43f6839f4d15810a56d368a076f2a43bd37c3ace Mon Sep 17 00:00:00 2001 From: tnoho Date: Tue, 11 Jul 2023 20:51:49 +0900 Subject: [PATCH 04/33] =?UTF-8?q?SoraAudioSinkImpl=20=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=B3=E3=82=B9=E3=83=88=E3=83=A9=E3=82=AF=E3=82=BF=E5=BC=95?= =?UTF-8?q?=E6=95=B0=E3=81=AB=E8=BF=BD=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_sink.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sora_audio_sink.h b/src/sora_audio_sink.h index 798ca6d7..fcfdc3af 100644 --- a/src/sora_audio_sink.h +++ b/src/sora_audio_sink.h @@ -23,13 +23,19 @@ namespace nb = nanobind; * Sora からの音声を受け取る SoraAudioSinkImpl です。 * * Connection の OnTrack コールバックから渡されるリモート Track から音声を取り出すことができます。 - * Track からの音声は SoraAudioSinkImpl 内のバッファに溜め込まれるため、任意のタイミングで音声を取り出すことができます。 + * Track からの音声はコンストラクタで設定したサンプリングレートとチャネル数に変換し、 + * SoraAudioSinkImpl 内のバッファに溜め込まれるため、任意のタイミングで音声を取り出すことができます。 * 実装上の留意点:Track の参照保持のための Impl のない SoraAudioSink を __init__.py に定義しています。 * SoraAudioSinkImpl を直接 Python から呼び出すことは想定していません。 */ class SoraAudioSinkImpl : public webrtc::AudioTrackSinkInterface, public DisposeSubscriber { public: + /** + * @param track 音声を取り出す OnTrack コールバックから渡されるリモート Track + * @param output_sample_rate 音声の出力サンプリングレート + * @param output_channels 音声の出力チャネル数 + */ SoraAudioSinkImpl(SoraTrackInterface* track, int output_sample_rate, size_t output_channels); From a055f3f094a009d65c91784f16c5e9be7f1c5cdf Mon Sep 17 00:00:00 2001 From: tnoho Date: Tue, 18 Jul 2023 18:29:00 +0900 Subject: [PATCH 05/33] =?UTF-8?q?AudioSink2=20=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + src/sora_audio_sink2.cpp | 132 +++++++++++++++++++++++++++++++++++++++ src/sora_audio_sink2.h | 64 +++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 src/sora_audio_sink2.cpp create mode 100644 src/sora_audio_sink2.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d4836252..35bebf97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,7 @@ nanobind_add_module( src/dummy_audio_mixer.cpp src/sora.cpp src/sora_audio_sink.cpp + src/sora_audio_sink2.cpp src/sora_audio_source.cpp src/sora_connection.cpp src/sora_factory.cpp diff --git a/src/sora_audio_sink2.cpp b/src/sora_audio_sink2.cpp new file mode 100644 index 00000000..02f9773f --- /dev/null +++ b/src/sora_audio_sink2.cpp @@ -0,0 +1,132 @@ +#include "sora_audio_sink2.h" + +#include + +// WebRTC +#include +#include + +SoraAudioSink2Impl::SoraAudioSink2Impl(SoraTrackInterface* track, + int output_sample_rate, + size_t output_channels) + : track_(track), + output_sample_rate_(output_sample_rate), + output_channels_(output_channels), + sample_rate_(0), + number_of_channels_(0) { + audio_frame_ = std::make_unique(); + track_->AddSubscriber(this); + webrtc::AudioTrackInterface* audio_track = + static_cast(track_->GetTrack().get()); + audio_track->AddSink(this); +} + +SoraAudioSink2Impl::~SoraAudioSink2Impl() { + Del(); +} + +void SoraAudioSink2Impl::Del() { + if (track_) { + track_->RemoveSubscriber(this); + } + Disposed(); +} + +void SoraAudioSink2Impl::Disposed() { + if (track_ && track_->GetTrack()) { + webrtc::AudioTrackInterface* audio_track = + static_cast(track_->GetTrack().get()); + audio_track->RemoveSink(this); + } + track_ = nullptr; +} + +void SoraAudioSink2Impl::PublisherDisposed() { + Disposed(); +} + +void SoraAudioSink2Impl::OnData( + const void* audio_data, + int bits_per_sample, + int sample_rate, + size_t number_of_channels, + size_t number_of_frames, + absl::optional absolute_capture_timestamp_ms) { + if (absolute_capture_timestamp_ms) { + audio_frame_->set_absolute_capture_timestamp_ms( + *absolute_capture_timestamp_ms); + } + // Resampling して sampling_rate を揃える + bool need_resample = + output_sample_rate_ != -1 && sample_rate != output_sample_rate_; + if (need_resample) { + int samples_per_channel_int = resampler_.Resample10Msec( + static_cast(audio_data), sample_rate, + output_sample_rate_, number_of_channels, + webrtc::AudioFrame::kMaxDataSizeSamples, audio_frame_->mutable_data()); + if (samples_per_channel_int < 0) { + return; + } + audio_frame_->samples_per_channel_ = + static_cast(samples_per_channel_int); + audio_frame_->sample_rate_hz_ = output_sample_rate_; + audio_frame_->num_channels_ = number_of_channels; + audio_frame_->channel_layout_ = + webrtc::GuessChannelLayout(number_of_channels); + } + // Remix して channel 数を揃える + bool need_remix = + output_channels_ != 0 && number_of_channels != output_channels_; + if (need_remix) { + if (!need_resample) { + audio_frame_->UpdateFrame( + audio_frame_->timestamp_, static_cast(audio_data), + number_of_frames, sample_rate, audio_frame_->speech_type_, + audio_frame_->vad_activity_, number_of_channels); + } + webrtc::RemixFrame(output_channels_, audio_frame_.get()); + } + + if (need_resample || need_remix) { + AppendData(audio_frame_->data(), audio_frame_->sample_rate_hz_, + audio_frame_->num_channels_, audio_frame_->samples_per_channel_); + } else { + AppendData(static_cast(audio_data), sample_rate, + number_of_channels, number_of_frames); + } +} + +void SoraAudioSink2Impl::AppendData(const int16_t* audio_data, + int sample_rate, + size_t number_of_channels, + size_t number_of_frames) { + { + std::unique_lock lock(buffer_mtx_); + + if (sample_rate_ != sample_rate || + number_of_channels_ != number_of_channels) { + /* 実行中にフォーマットが変更されることは想定しないはずなので、その場合はエラーに落とすようにする */ + sample_rate_ = sample_rate; + number_of_channels_ = number_of_channels; + if (on_format_) { + on_format_(sample_rate_, number_of_channels_); + } + } + + const size_t num_elements = number_of_channels_ * number_of_frames; + buffer_.AppendData(num_elements, [&](rtc::ArrayView buf) { + memcpy(buf.data(), audio_data, num_elements * sizeof(int16_t)); + return num_elements; + }); + + buffer_cond_.notify_all(); + } + + if (on_data_) { + size_t shape[2] = {number_of_frames, number_of_channels_}; + auto data = nb::ndarray>( + (void*)audio_data, 2, shape); + /* まだ使ったことながない。現状 Python 側で on_frame と同じ感覚でコールバックの外に値を持ち出すと落ちるはず。 */ + on_data_(data); + } +} \ No newline at end of file diff --git a/src/sora_audio_sink2.h b/src/sora_audio_sink2.h new file mode 100644 index 00000000..558a672c --- /dev/null +++ b/src/sora_audio_sink2.h @@ -0,0 +1,64 @@ +#ifndef SORA_AUDIO_SINK2_H_ +#define SORA_AUDIO_SINK2_H_ + +#include +#include + +// nonobind +#include +#include + +// WebRTC +#include +#include +#include +#include +#include + +#include "sora_track_interface.h" + +namespace nb = nanobind; + +class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, + public DisposeSubscriber { + public: + SoraAudioSink2Impl(SoraTrackInterface* track, + int output_sample_rate, + size_t output_channels); + ~SoraAudioSink2Impl(); + + void Del(); + void Disposed(); + void PublisherDisposed() override; + // webrtc::AudioTrackSinkInterface + void OnData(const void* audio_data, + int bits_per_sample, + int sample_rate, + size_t number_of_channels, + size_t number_of_frames, + absl::optional absolute_capture_timestamp_ms) override; + + std::function>)> + on_data_; + std::function on_format_; + + private: + void SendData(const int16_t* audio_data, + int sample_rate, + size_t number_of_channels, + size_t number_of_frames); + + SoraTrackInterface* track_; + const int output_sample_rate_; + const size_t output_channels_; + std::unique_ptr audio_frame_; + webrtc::acm2::ACMResampler resampler_; + std::mutex buffer_mtx_; + std::condition_variable buffer_cond_; + rtc::BufferT buffer_; + int sample_rate_; + size_t number_of_channels_; +}; + +#endif \ No newline at end of file From d73c113e1ae6de1e169d9c5fd64e4b41a66527b3 Mon Sep 17 00:00:00 2001 From: tnoho Date: Tue, 18 Jul 2023 20:57:20 +0900 Subject: [PATCH 06/33] =?UTF-8?q?AudioSink2=20=E3=81=AE=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=E3=82=92=E6=9C=80=E9=81=A9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_sink2.cpp | 122 ++++++++++++++++++--------------------- src/sora_audio_sink2.h | 38 ++++++------ 2 files changed, 74 insertions(+), 86 deletions(-) diff --git a/src/sora_audio_sink2.cpp b/src/sora_audio_sink2.cpp index 02f9773f..63fc86f1 100644 --- a/src/sora_audio_sink2.cpp +++ b/src/sora_audio_sink2.cpp @@ -6,15 +6,49 @@ #include #include +SoraAudioFrame::SoraAudioFrame(std::unique_ptr audio_frame) + : audio_frame_(std::move(audio_frame)) {} + +nb::ndarray> +SoraAudioFrame::Data() const { + size_t shape[2] = {static_cast(audio_frame_->samples_per_channel()), + static_cast(audio_frame_->num_channels())}; + return nb::ndarray>( + (int16_t*)audio_frame_->data(), 2, shape); +} + +size_t SoraAudioFrame::samples_per_channel() const { + return audio_frame_->samples_per_channel(); +} + +size_t SoraAudioFrame::num_channels() const { + return audio_frame_->num_channels(); +} + +int SoraAudioFrame::sample_rate_hz() const { + return audio_frame_->sample_rate_hz(); +} + +std::optional SoraAudioFrame::absolute_capture_timestamp_ms() const { + if (audio_frame_->absolute_capture_timestamp_ms()) { + std::optional value = + *audio_frame_->absolute_capture_timestamp_ms(); + return value; + } else { + return std::nullopt; + } +} + +webrtc::AudioFrame::VADActivity SoraAudioFrame::vad_activity() const { + return audio_frame_->vad_activity_; +} + SoraAudioSink2Impl::SoraAudioSink2Impl(SoraTrackInterface* track, int output_sample_rate, size_t output_channels) : track_(track), output_sample_rate_(output_sample_rate), - output_channels_(output_channels), - sample_rate_(0), - number_of_channels_(0) { - audio_frame_ = std::make_unique(); + output_channels_(output_channels) { track_->AddSubscriber(this); webrtc::AudioTrackInterface* audio_track = static_cast(track_->GetTrack().get()); @@ -52,81 +86,35 @@ void SoraAudioSink2Impl::OnData( size_t number_of_channels, size_t number_of_frames, absl::optional absolute_capture_timestamp_ms) { + auto tuned_frame = std::make_unique(); + tuned_frame->UpdateFrame( + 0, static_cast(audio_data), number_of_frames, sample_rate, + webrtc::AudioFrame::SpeechType::kUndefined, + webrtc::AudioFrame::VADActivity::kVadUnknown, number_of_channels); if (absolute_capture_timestamp_ms) { - audio_frame_->set_absolute_capture_timestamp_ms( + tuned_frame->set_absolute_capture_timestamp_ms( *absolute_capture_timestamp_ms); } // Resampling して sampling_rate を揃える - bool need_resample = - output_sample_rate_ != -1 && sample_rate != output_sample_rate_; + bool need_resample = output_sample_rate_ != -1 && + tuned_frame->sample_rate_hz() != output_sample_rate_; if (need_resample) { int samples_per_channel_int = resampler_.Resample10Msec( - static_cast(audio_data), sample_rate, - output_sample_rate_, number_of_channels, - webrtc::AudioFrame::kMaxDataSizeSamples, audio_frame_->mutable_data()); + tuned_frame->data(), tuned_frame->sample_rate_hz(), output_sample_rate_, + tuned_frame->num_channels(), webrtc::AudioFrame::kMaxDataSizeSamples, + tuned_frame->mutable_data()); if (samples_per_channel_int < 0) { return; } - audio_frame_->samples_per_channel_ = + tuned_frame->samples_per_channel_ = static_cast(samples_per_channel_int); - audio_frame_->sample_rate_hz_ = output_sample_rate_; - audio_frame_->num_channels_ = number_of_channels; - audio_frame_->channel_layout_ = - webrtc::GuessChannelLayout(number_of_channels); + tuned_frame->sample_rate_hz_ = output_sample_rate_; } // Remix して channel 数を揃える - bool need_remix = - output_channels_ != 0 && number_of_channels != output_channels_; - if (need_remix) { - if (!need_resample) { - audio_frame_->UpdateFrame( - audio_frame_->timestamp_, static_cast(audio_data), - number_of_frames, sample_rate, audio_frame_->speech_type_, - audio_frame_->vad_activity_, number_of_channels); - } - webrtc::RemixFrame(output_channels_, audio_frame_.get()); + if (output_channels_ != 0 && + tuned_frame->num_channels() != output_channels_) { + webrtc::RemixFrame(output_channels_, tuned_frame.get()); } - if (need_resample || need_remix) { - AppendData(audio_frame_->data(), audio_frame_->sample_rate_hz_, - audio_frame_->num_channels_, audio_frame_->samples_per_channel_); - } else { - AppendData(static_cast(audio_data), sample_rate, - number_of_channels, number_of_frames); - } + on_frame_(std::make_shared(std::move(tuned_frame))); } - -void SoraAudioSink2Impl::AppendData(const int16_t* audio_data, - int sample_rate, - size_t number_of_channels, - size_t number_of_frames) { - { - std::unique_lock lock(buffer_mtx_); - - if (sample_rate_ != sample_rate || - number_of_channels_ != number_of_channels) { - /* 実行中にフォーマットが変更されることは想定しないはずなので、その場合はエラーに落とすようにする */ - sample_rate_ = sample_rate; - number_of_channels_ = number_of_channels; - if (on_format_) { - on_format_(sample_rate_, number_of_channels_); - } - } - - const size_t num_elements = number_of_channels_ * number_of_frames; - buffer_.AppendData(num_elements, [&](rtc::ArrayView buf) { - memcpy(buf.data(), audio_data, num_elements * sizeof(int16_t)); - return num_elements; - }); - - buffer_cond_.notify_all(); - } - - if (on_data_) { - size_t shape[2] = {number_of_frames, number_of_channels_}; - auto data = nb::ndarray>( - (void*)audio_data, 2, shape); - /* まだ使ったことながない。現状 Python 側で on_frame と同じ感覚でコールバックの外に値を持ち出すと落ちるはず。 */ - on_data_(data); - } -} \ No newline at end of file diff --git a/src/sora_audio_sink2.h b/src/sora_audio_sink2.h index 558a672c..4eb260cd 100644 --- a/src/sora_audio_sink2.h +++ b/src/sora_audio_sink2.h @@ -1,9 +1,6 @@ #ifndef SORA_AUDIO_SINK2_H_ #define SORA_AUDIO_SINK2_H_ -#include -#include - // nonobind #include #include @@ -13,12 +10,26 @@ #include #include #include -#include #include "sora_track_interface.h" namespace nb = nanobind; +class SoraAudioFrame { + public: + SoraAudioFrame(std::unique_ptr audio_frame); + + nb::ndarray> Data() const; + size_t samples_per_channel() const; + size_t num_channels() const; + int sample_rate_hz() const; + std::optional absolute_capture_timestamp_ms() const; + webrtc::AudioFrame::VADActivity vad_activity() const; + + private: + std::unique_ptr audio_frame_; +}; + class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, public DisposeSubscriber { public: @@ -38,27 +49,16 @@ class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, size_t number_of_frames, absl::optional absolute_capture_timestamp_ms) override; - std::function>)> - on_data_; - std::function on_format_; + // このコールバックは shared_ptr にしないとリークする + std::function)> on_frame_; private: - void SendData(const int16_t* audio_data, - int sample_rate, - size_t number_of_channels, - size_t number_of_frames); - SoraTrackInterface* track_; const int output_sample_rate_; const size_t output_channels_; - std::unique_ptr audio_frame_; + // ACMResampler の前に std::unique_ptr がなんでも良いので無いと何故かビルドが通らない + std::unique_ptr dummy_; webrtc::acm2::ACMResampler resampler_; - std::mutex buffer_mtx_; - std::condition_variable buffer_cond_; - rtc::BufferT buffer_; - int sample_rate_; - size_t number_of_channels_; }; #endif \ No newline at end of file From cb5bc9933ddb2c761d95d1c7543deebb82ed60ea Mon Sep 17 00:00:00 2001 From: tnoho Date: Tue, 18 Jul 2023 21:09:39 +0900 Subject: [PATCH 07/33] =?UTF-8?q?AudioSink2=20=E3=81=AE=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_sdk/__init__.py | 10 ++++++++++ src/sora_sdk_ext.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/sora_sdk/__init__.py b/src/sora_sdk/__init__.py index 014f6f13..33c42967 100644 --- a/src/sora_sdk/__init__.py +++ b/src/sora_sdk/__init__.py @@ -18,6 +18,16 @@ def __del__(self): del self.__track +class SoraAudioSink2(SoraAudioSink2Impl): + def __init__(self, track, output_frequency, output_channels): + super().__init__(track, output_frequency, output_channels) + self.__track = track + + def __del__(self): + super().__del__() + del self.__track + + class SoraVideoSink(SoraVideoSinkImpl): def __init__(self, track): super().__init__(track) diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index de7593a9..6eb9e757 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -8,6 +8,7 @@ #include "sora.h" #include "sora_audio_sink.h" +#include "sora_audio_sink2.h" #include "sora_audio_source.h" #include "sora_connection.h" #include "sora_log.h" @@ -44,6 +45,21 @@ PyType_Slot audio_sink_slots[] = { {Py_tp_traverse, (void*)audio_sink_tp_traverse}, {0, nullptr}}; +int audio_sink2_tp_traverse(PyObject* self, visitproc visit, void* arg) { + SoraAudioSink2Impl* audio_sink = nb::inst_ptr(self); + + if (audio_sink->on_frame_) { + nb::object on_frame = nb::cast(audio_sink->on_frame_, nb::rv_policy::none); + Py_VISIT(on_frame.ptr()); + } + + return 0; +} + +PyType_Slot audio_sink2_slots[] = { + {Py_tp_traverse, (void*)audio_sink2_tp_traverse}, + {0, nullptr}}; + int video_sink_tp_traverse(PyObject* self, visitproc visit, void* arg) { SoraVideoSinkImpl* video_sink = nb::inst_ptr(self); @@ -130,6 +146,12 @@ NB_MODULE(sora_sdk_ext, m) { .value("LIVE", webrtc::MediaStreamTrackInterface::TrackState::kLive) .value("ENDED", webrtc::MediaStreamTrackInterface::TrackState::kEnded); + nb::enum_(m, "VADActivity", + nb::is_arithmetic()) + .value("ACTIVE", webrtc::AudioFrame::VADActivity::kVadActive) + .value("PASSIVE", webrtc::AudioFrame::VADActivity::kVadPassive) + .value("UNKNOWN", webrtc::AudioFrame::VADActivity::kVadUnknown); + nb::enum_(m, "SoraLoggingSeverity", nb::is_arithmetic()) .value("VERBOSE", rtc::LoggingSeverity::LS_VERBOSE) .value("INFO", rtc::LoggingSeverity::LS_INFO) @@ -187,6 +209,22 @@ NB_MODULE(sora_sdk_ext, m) { .def_rw("on_data", &SoraAudioSinkImpl::on_data_) .def_rw("on_format", &SoraAudioSinkImpl::on_format_); + nb::class_(m, "SoraAudioFrame") + .def_prop_ro("samples_per_channel", &SoraAudioFrame::samples_per_channel) + .def_prop_ro("num_channels", &SoraAudioFrame::num_channels) + .def_prop_ro("sample_rate_hz", &SoraAudioFrame::sample_rate_hz) + .def_prop_ro("absolute_capture_timestamp_ms", + &SoraAudioFrame::absolute_capture_timestamp_ms) + .def("data", &SoraAudioFrame::Data, nb::rv_policy::reference) + .def_prop_ro("vad_activity", &SoraAudioFrame::vad_activity); + + nb::class_(m, "SoraAudioSink2Impl", + nb::type_slots(audio_sink2_slots)) + .def(nb::init(), "track"_a, + "output_frequency"_a = -1, "output_channels"_a = 0) + .def("__del__", &SoraAudioSink2Impl::Del) + .def_rw("on_frame", &SoraAudioSink2Impl::on_frame_); + nb::class_(m, "SoraVideoFrame") .def("data", &SoraVideoFrame::Data, nb::rv_policy::reference); From 3db4cac3c8416928eada45621540a6791bb3c3bd Mon Sep 17 00:00:00 2001 From: tnoho Date: Tue, 18 Jul 2023 22:05:05 +0900 Subject: [PATCH 08/33] =?UTF-8?q?WebRtcVad=20=E3=82=92=E5=85=A5=E3=82=8C?= =?UTF-8?q?=E3=81=A6=E3=81=BF=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_sink2.cpp | 17 +++++++++++++++++ src/sora_audio_sink2.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/src/sora_audio_sink2.cpp b/src/sora_audio_sink2.cpp index 63fc86f1..c0ab2d5a 100644 --- a/src/sora_audio_sink2.cpp +++ b/src/sora_audio_sink2.cpp @@ -49,6 +49,10 @@ SoraAudioSink2Impl::SoraAudioSink2Impl(SoraTrackInterface* track, : track_(track), output_sample_rate_(output_sample_rate), output_channels_(output_channels) { + vad_instance_ = WebRtcVad_Create(); + WebRtcVad_Init(vad_instance_); + // 0 がノーマルモード + WebRtcVad_set_mode(vad_instance_, 0); track_->AddSubscriber(this); webrtc::AudioTrackInterface* audio_track = static_cast(track_->GetTrack().get()); @@ -72,6 +76,9 @@ void SoraAudioSink2Impl::Disposed() { static_cast(track_->GetTrack().get()); audio_track->RemoveSink(this); } + if (vad_instance_) { + WebRtcVad_Free(vad_instance_); + } track_ = nullptr; } @@ -115,6 +122,16 @@ void SoraAudioSink2Impl::OnData( tuned_frame->num_channels() != output_channels_) { webrtc::RemixFrame(output_channels_, tuned_frame.get()); } + if (vad_instance_) { + int vad_return = WebRtcVad_Process( + vad_instance_, tuned_frame->sample_rate_hz(), tuned_frame->data(), + tuned_frame->samples_per_channel_); + if (vad_return == 1) { + tuned_frame->vad_activity_ = webrtc::AudioFrame::VADActivity::kVadActive; + } else { + tuned_frame->vad_activity_ = webrtc::AudioFrame::VADActivity::kVadPassive; + } + } on_frame_(std::make_shared(std::move(tuned_frame))); } diff --git a/src/sora_audio_sink2.h b/src/sora_audio_sink2.h index 4eb260cd..dea41ceb 100644 --- a/src/sora_audio_sink2.h +++ b/src/sora_audio_sink2.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "sora_track_interface.h" @@ -59,6 +60,7 @@ class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, // ACMResampler の前に std::unique_ptr がなんでも良いので無いと何故かビルドが通らない std::unique_ptr dummy_; webrtc::acm2::ACMResampler resampler_; + VadInst* vad_instance_; }; #endif \ No newline at end of file From 0ca347c8c8cfc55ad2eb01ffe048b0c6761d0a6b Mon Sep 17 00:00:00 2001 From: tnoho Date: Thu, 20 Jul 2023 15:31:14 +0900 Subject: [PATCH 09/33] =?UTF-8?q?RNNVAD=20=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_sink2.cpp | 60 ++++++++++++++++++++++++++-------------- src/sora_audio_sink2.h | 14 ++++++++-- src/sora_sdk_ext.cpp | 8 +----- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/sora_audio_sink2.cpp b/src/sora_audio_sink2.cpp index c0ab2d5a..08eb752c 100644 --- a/src/sora_audio_sink2.cpp +++ b/src/sora_audio_sink2.cpp @@ -5,9 +5,15 @@ // WebRTC #include #include +#include +#include +#include +#include -SoraAudioFrame::SoraAudioFrame(std::unique_ptr audio_frame) - : audio_frame_(std::move(audio_frame)) {} +SoraAudioFrame::SoraAudioFrame(std::unique_ptr audio_frame, + float voice_probability) + : audio_frame_(std::move(audio_frame)), + voice_probability_(voice_probability) {} nb::ndarray> SoraAudioFrame::Data() const { @@ -39,8 +45,8 @@ std::optional SoraAudioFrame::absolute_capture_timestamp_ms() const { } } -webrtc::AudioFrame::VADActivity SoraAudioFrame::vad_activity() const { - return audio_frame_->vad_activity_; +float SoraAudioFrame::voice_probability() const { + return voice_probability_; } SoraAudioSink2Impl::SoraAudioSink2Impl(SoraTrackInterface* track, @@ -48,11 +54,14 @@ SoraAudioSink2Impl::SoraAudioSink2Impl(SoraTrackInterface* track, size_t output_channels) : track_(track), output_sample_rate_(output_sample_rate), - output_channels_(output_channels) { - vad_instance_ = WebRtcVad_Create(); - WebRtcVad_Init(vad_instance_); - // 0 がノーマルモード - WebRtcVad_set_mode(vad_instance_, 0); + output_channels_(output_channels), + vad_input_config_(0) { + vad_ = std::make_unique( + webrtc::kVadResetPeriodMs, // libWebRTC 内部の設定に合わせる + webrtc::GetAvailableCpuFeatures(), + webrtc::rnn_vad:: + kSampleRate24kHz // 24kHz にしておかないと、VAD 前にリサンプリングが走る + ); track_->AddSubscriber(this); webrtc::AudioTrackInterface* audio_track = static_cast(track_->GetTrack().get()); @@ -76,9 +85,6 @@ void SoraAudioSink2Impl::Disposed() { static_cast(track_->GetTrack().get()); audio_track->RemoveSink(this); } - if (vad_instance_) { - WebRtcVad_Free(vad_instance_); - } track_ = nullptr; } @@ -122,16 +128,28 @@ void SoraAudioSink2Impl::OnData( tuned_frame->num_channels() != output_channels_) { webrtc::RemixFrame(output_channels_, tuned_frame.get()); } - if (vad_instance_) { - int vad_return = WebRtcVad_Process( - vad_instance_, tuned_frame->sample_rate_hz(), tuned_frame->data(), - tuned_frame->samples_per_channel_); - if (vad_return == 1) { - tuned_frame->vad_activity_ = webrtc::AudioFrame::VADActivity::kVadActive; - } else { - tuned_frame->vad_activity_ = webrtc::AudioFrame::VADActivity::kVadPassive; + float voice_probability = 0; + if (vad_) { + if (!audio_buffer_ || + vad_input_config_.sample_rate_hz() != tuned_frame->sample_rate_hz() || + vad_input_config_.num_channels() != tuned_frame->num_channels()) { + audio_buffer_.reset(new webrtc::AudioBuffer( + tuned_frame->sample_rate_hz(), tuned_frame->num_channels(), + webrtc::rnn_vad::kSampleRate24kHz, // VAD は 24kHz なので合わせる + 1, // VAD は 1 チャンネルなので合わせる + webrtc::rnn_vad:: + kSampleRate24kHz, // 出力はしないが、余計なインスタンスを生成しないよう合わせる + 1 // 出力はしないが VAD とチャネル数は合わせておく + )); + vad_input_config_ = webrtc::StreamConfig(tuned_frame->sample_rate_hz(), + tuned_frame->num_channels()); } + audio_buffer_->CopyFrom(tuned_frame->data(), vad_input_config_); + voice_probability = vad_->Analyze(webrtc::AudioFrameView( + audio_buffer_->channels(), audio_buffer_->num_channels(), + audio_buffer_->num_frames())); } - on_frame_(std::make_shared(std::move(tuned_frame))); + on_frame_(std::make_shared(std::move(tuned_frame), + voice_probability)); } diff --git a/src/sora_audio_sink2.h b/src/sora_audio_sink2.h index dea41ceb..527508b2 100644 --- a/src/sora_audio_sink2.h +++ b/src/sora_audio_sink2.h @@ -11,6 +11,10 @@ #include #include #include +#include +#include +#include +#include #include "sora_track_interface.h" @@ -18,17 +22,19 @@ namespace nb = nanobind; class SoraAudioFrame { public: - SoraAudioFrame(std::unique_ptr audio_frame); + SoraAudioFrame(std::unique_ptr audio_frame, + float voice_probability); nb::ndarray> Data() const; size_t samples_per_channel() const; size_t num_channels() const; int sample_rate_hz() const; std::optional absolute_capture_timestamp_ms() const; - webrtc::AudioFrame::VADActivity vad_activity() const; + float voice_probability() const; private: std::unique_ptr audio_frame_; + float voice_probability_; }; class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, @@ -60,7 +66,9 @@ class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, // ACMResampler の前に std::unique_ptr がなんでも良いので無いと何故かビルドが通らない std::unique_ptr dummy_; webrtc::acm2::ACMResampler resampler_; - VadInst* vad_instance_; + std::unique_ptr audio_buffer_; + webrtc::StreamConfig vad_input_config_; + std::unique_ptr vad_; }; #endif \ No newline at end of file diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index 6eb9e757..40455ef0 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -146,12 +146,6 @@ NB_MODULE(sora_sdk_ext, m) { .value("LIVE", webrtc::MediaStreamTrackInterface::TrackState::kLive) .value("ENDED", webrtc::MediaStreamTrackInterface::TrackState::kEnded); - nb::enum_(m, "VADActivity", - nb::is_arithmetic()) - .value("ACTIVE", webrtc::AudioFrame::VADActivity::kVadActive) - .value("PASSIVE", webrtc::AudioFrame::VADActivity::kVadPassive) - .value("UNKNOWN", webrtc::AudioFrame::VADActivity::kVadUnknown); - nb::enum_(m, "SoraLoggingSeverity", nb::is_arithmetic()) .value("VERBOSE", rtc::LoggingSeverity::LS_VERBOSE) .value("INFO", rtc::LoggingSeverity::LS_INFO) @@ -216,7 +210,7 @@ NB_MODULE(sora_sdk_ext, m) { .def_prop_ro("absolute_capture_timestamp_ms", &SoraAudioFrame::absolute_capture_timestamp_ms) .def("data", &SoraAudioFrame::Data, nb::rv_policy::reference) - .def_prop_ro("vad_activity", &SoraAudioFrame::vad_activity); + .def_prop_ro("voice_probability", &SoraAudioFrame::voice_probability); nb::class_(m, "SoraAudioSink2Impl", nb::type_slots(audio_sink2_slots)) From c09b3b29e31bc5304bd204ee0e0d2366d22376f2 Mon Sep 17 00:00:00 2001 From: tnoho Date: Thu, 20 Jul 2023 19:29:04 +0900 Subject: [PATCH 10/33] =?UTF-8?q?VAD=20=E3=82=92=E5=88=86=E5=89=B2?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + src/sora_audio_sink2.cpp | 47 +++++++--------------------------------- src/sora_audio_sink2.h | 14 ++---------- src/sora_sdk_ext.cpp | 8 +++++-- src/sora_vad.cpp | 41 +++++++++++++++++++++++++++++++++++ src/sora_vad.h | 30 +++++++++++++++++++++++++ 6 files changed, 88 insertions(+), 53 deletions(-) create mode 100644 src/sora_vad.cpp create mode 100644 src/sora_vad.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 35bebf97..8f819036 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ nanobind_add_module( src/sora_factory.cpp src/sora_log.cpp src/sora_sdk_ext.cpp + src/sora_vad.cpp src/sora_video_sink.cpp src/sora_video_source.cpp ) diff --git a/src/sora_audio_sink2.cpp b/src/sora_audio_sink2.cpp index 08eb752c..51cc0f37 100644 --- a/src/sora_audio_sink2.cpp +++ b/src/sora_audio_sink2.cpp @@ -10,10 +10,8 @@ #include #include -SoraAudioFrame::SoraAudioFrame(std::unique_ptr audio_frame, - float voice_probability) - : audio_frame_(std::move(audio_frame)), - voice_probability_(voice_probability) {} +SoraAudioFrame::SoraAudioFrame(std::unique_ptr audio_frame) + : audio_frame_(std::move(audio_frame)) {} nb::ndarray> SoraAudioFrame::Data() const { @@ -23,6 +21,10 @@ SoraAudioFrame::Data() const { (int16_t*)audio_frame_->data(), 2, shape); } +const int16_t* SoraAudioFrame::RawData() const { + return audio_frame_->data(); +} + size_t SoraAudioFrame::samples_per_channel() const { return audio_frame_->samples_per_channel(); } @@ -45,23 +47,12 @@ std::optional SoraAudioFrame::absolute_capture_timestamp_ms() const { } } -float SoraAudioFrame::voice_probability() const { - return voice_probability_; -} - SoraAudioSink2Impl::SoraAudioSink2Impl(SoraTrackInterface* track, int output_sample_rate, size_t output_channels) : track_(track), output_sample_rate_(output_sample_rate), - output_channels_(output_channels), - vad_input_config_(0) { - vad_ = std::make_unique( - webrtc::kVadResetPeriodMs, // libWebRTC 内部の設定に合わせる - webrtc::GetAvailableCpuFeatures(), - webrtc::rnn_vad:: - kSampleRate24kHz // 24kHz にしておかないと、VAD 前にリサンプリングが走る - ); + output_channels_(output_channels) { track_->AddSubscriber(this); webrtc::AudioTrackInterface* audio_track = static_cast(track_->GetTrack().get()); @@ -128,28 +119,6 @@ void SoraAudioSink2Impl::OnData( tuned_frame->num_channels() != output_channels_) { webrtc::RemixFrame(output_channels_, tuned_frame.get()); } - float voice_probability = 0; - if (vad_) { - if (!audio_buffer_ || - vad_input_config_.sample_rate_hz() != tuned_frame->sample_rate_hz() || - vad_input_config_.num_channels() != tuned_frame->num_channels()) { - audio_buffer_.reset(new webrtc::AudioBuffer( - tuned_frame->sample_rate_hz(), tuned_frame->num_channels(), - webrtc::rnn_vad::kSampleRate24kHz, // VAD は 24kHz なので合わせる - 1, // VAD は 1 チャンネルなので合わせる - webrtc::rnn_vad:: - kSampleRate24kHz, // 出力はしないが、余計なインスタンスを生成しないよう合わせる - 1 // 出力はしないが VAD とチャネル数は合わせておく - )); - vad_input_config_ = webrtc::StreamConfig(tuned_frame->sample_rate_hz(), - tuned_frame->num_channels()); - } - audio_buffer_->CopyFrom(tuned_frame->data(), vad_input_config_); - voice_probability = vad_->Analyze(webrtc::AudioFrameView( - audio_buffer_->channels(), audio_buffer_->num_channels(), - audio_buffer_->num_frames())); - } - on_frame_(std::make_shared(std::move(tuned_frame), - voice_probability)); + on_frame_(std::make_shared(std::move(tuned_frame))); } diff --git a/src/sora_audio_sink2.h b/src/sora_audio_sink2.h index 527508b2..0456afee 100644 --- a/src/sora_audio_sink2.h +++ b/src/sora_audio_sink2.h @@ -9,12 +9,7 @@ #include #include #include -#include #include -#include -#include -#include -#include #include "sora_track_interface.h" @@ -22,19 +17,17 @@ namespace nb = nanobind; class SoraAudioFrame { public: - SoraAudioFrame(std::unique_ptr audio_frame, - float voice_probability); + SoraAudioFrame(std::unique_ptr audio_frame); nb::ndarray> Data() const; + const int16_t* RawData() const; size_t samples_per_channel() const; size_t num_channels() const; int sample_rate_hz() const; std::optional absolute_capture_timestamp_ms() const; - float voice_probability() const; private: std::unique_ptr audio_frame_; - float voice_probability_; }; class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, @@ -66,9 +59,6 @@ class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, // ACMResampler の前に std::unique_ptr がなんでも良いので無いと何故かビルドが通らない std::unique_ptr dummy_; webrtc::acm2::ACMResampler resampler_; - std::unique_ptr audio_buffer_; - webrtc::StreamConfig vad_input_config_; - std::unique_ptr vad_; }; #endif \ No newline at end of file diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index 40455ef0..b9472f19 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -13,6 +13,7 @@ #include "sora_connection.h" #include "sora_log.h" #include "sora_track_interface.h" +#include "sora_vad.h" #include "sora_video_sink.h" #include "sora_video_source.h" @@ -209,8 +210,7 @@ NB_MODULE(sora_sdk_ext, m) { .def_prop_ro("sample_rate_hz", &SoraAudioFrame::sample_rate_hz) .def_prop_ro("absolute_capture_timestamp_ms", &SoraAudioFrame::absolute_capture_timestamp_ms) - .def("data", &SoraAudioFrame::Data, nb::rv_policy::reference) - .def_prop_ro("voice_probability", &SoraAudioFrame::voice_probability); + .def("data", &SoraAudioFrame::Data, nb::rv_policy::reference); nb::class_(m, "SoraAudioSink2Impl", nb::type_slots(audio_sink2_slots)) @@ -219,6 +219,10 @@ NB_MODULE(sora_sdk_ext, m) { .def("__del__", &SoraAudioSink2Impl::Del) .def_rw("on_frame", &SoraAudioSink2Impl::on_frame_); + nb::class_(m, "SoraVAD") + .def(nb::init<>()) + .def("analyze", &SoraVAD::Analyze); + nb::class_(m, "SoraVideoFrame") .def("data", &SoraVideoFrame::Data, nb::rv_policy::reference); diff --git a/src/sora_vad.cpp b/src/sora_vad.cpp new file mode 100644 index 00000000..99c36729 --- /dev/null +++ b/src/sora_vad.cpp @@ -0,0 +1,41 @@ +#include "sora_vad.h" + +#include + +// WebRTC +#include +#include +#include +#include +#include +#include + +SoraVAD::SoraVAD() { + vad_ = std::make_unique( + webrtc::kVadResetPeriodMs, // libWebRTC 内部の設定に合わせる + webrtc::GetAvailableCpuFeatures(), + webrtc::rnn_vad:: + kSampleRate24kHz // 24kHz にしておかないと、VAD 前にリサンプリングが走る + ); +} + +float SoraVAD::Analyze(std::shared_ptr frame) { + if (!audio_buffer_ || + vad_input_config_.sample_rate_hz() != frame->sample_rate_hz() || + vad_input_config_.num_channels() != frame->num_channels()) { + audio_buffer_.reset(new webrtc::AudioBuffer( + frame->sample_rate_hz(), frame->num_channels(), + webrtc::rnn_vad::kSampleRate24kHz, // VAD は 24kHz なので合わせる + 1, // VAD は 1 チャンネルなので合わせる + webrtc::rnn_vad:: + kSampleRate24kHz, // 出力はしないが、余計なインスタンスを生成しないよう合わせる + 1 // 出力はしないが VAD とチャネル数は合わせておく + )); + vad_input_config_ = + webrtc::StreamConfig(frame->sample_rate_hz(), frame->num_channels()); + } + audio_buffer_->CopyFrom(frame->RawData(), vad_input_config_); + return vad_->Analyze(webrtc::AudioFrameView( + audio_buffer_->channels(), audio_buffer_->num_channels(), + audio_buffer_->num_frames())); +} diff --git a/src/sora_vad.h b/src/sora_vad.h new file mode 100644 index 00000000..eea3e18b --- /dev/null +++ b/src/sora_vad.h @@ -0,0 +1,30 @@ +#ifndef SORA_VAD_H_ +#define SORA_VAD_H_ + +// nonobind +#include +#include + +// WebRTC +#include +#include +#include +#include + +#include "sora_audio_sink2.h" + +namespace nb = nanobind; + +class SoraVAD { + public: + SoraVAD(); + + float Analyze(std::shared_ptr frame); + + private: + std::unique_ptr audio_buffer_; + webrtc::StreamConfig vad_input_config_; + std::unique_ptr vad_; +}; + +#endif \ No newline at end of file From 92e8fc3ded64f950bd376984b9c91aa70cafac88 Mon Sep 17 00:00:00 2001 From: tnoho Date: Mon, 24 Jul 2023 10:48:35 +0900 Subject: [PATCH 11/33] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9A=E3=81=AF=20pickle=20=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_sink2.cpp | 42 ++++++++++++++++++++++++++++++++++++---- src/sora_audio_sink2.h | 9 +++++++++ src/sora_sdk_ext.cpp | 16 +++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/sora_audio_sink2.cpp b/src/sora_audio_sink2.cpp index 51cc0f37..941acfa1 100644 --- a/src/sora_audio_sink2.cpp +++ b/src/sora_audio_sink2.cpp @@ -13,8 +13,18 @@ SoraAudioFrame::SoraAudioFrame(std::unique_ptr audio_frame) : audio_frame_(std::move(audio_frame)) {} +SoraAudioFrame::SoraAudioFrame(std::vector vector, + size_t samples_per_channel, + size_t num_channels, + int sample_rate_hz) + : vector_(vector), + samples_per_channel_(samples_per_channel), + num_channels_(num_channels), + sample_rate_hz_(sample_rate_hz) {} + nb::ndarray> SoraAudioFrame::Data() const { + // Data はまだ vector の時は返せてない size_t shape[2] = {static_cast(audio_frame_->samples_per_channel()), static_cast(audio_frame_->num_channels())}; return nb::ndarray>( @@ -22,19 +32,43 @@ SoraAudioFrame::Data() const { } const int16_t* SoraAudioFrame::RawData() const { - return audio_frame_->data(); + if (audio_frame_) { + return audio_frame_->data(); + } else { + return (const int16_t*)vector_.data(); + } +} + +std::vector SoraAudioFrame::VectorData() const { + std::vector vector( + audio_frame_->data(), + audio_frame_->data() + + audio_frame_->samples_per_channel() * audio_frame_->num_channels()); + return vector; } size_t SoraAudioFrame::samples_per_channel() const { - return audio_frame_->samples_per_channel(); + if (audio_frame_) { + return audio_frame_->samples_per_channel(); + } else { + return samples_per_channel_; + } } size_t SoraAudioFrame::num_channels() const { - return audio_frame_->num_channels(); + if (audio_frame_) { + return audio_frame_->num_channels(); + } else { + return num_channels_; + } } int SoraAudioFrame::sample_rate_hz() const { - return audio_frame_->sample_rate_hz(); + if (audio_frame_) { + return audio_frame_->sample_rate_hz(); + } else { + return sample_rate_hz_; + } } std::optional SoraAudioFrame::absolute_capture_timestamp_ms() const { diff --git a/src/sora_audio_sink2.h b/src/sora_audio_sink2.h index 0456afee..93d6a3ff 100644 --- a/src/sora_audio_sink2.h +++ b/src/sora_audio_sink2.h @@ -18,9 +18,14 @@ namespace nb = nanobind; class SoraAudioFrame { public: SoraAudioFrame(std::unique_ptr audio_frame); + SoraAudioFrame(std::vector vector, + size_t samples_per_channel, + size_t num_channels, + int sample_rate_hz); nb::ndarray> Data() const; const int16_t* RawData() const; + std::vector VectorData() const; size_t samples_per_channel() const; size_t num_channels() const; int sample_rate_hz() const; @@ -28,6 +33,10 @@ class SoraAudioFrame { private: std::unique_ptr audio_frame_; + std::vector vector_; + size_t samples_per_channel_; + size_t num_channels_; + int sample_rate_hz_; }; class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index b9472f19..1f4750a4 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "sora.h" #include "sora_audio_sink.h" @@ -205,6 +207,20 @@ NB_MODULE(sora_sdk_ext, m) { .def_rw("on_format", &SoraAudioSinkImpl::on_format_); nb::class_(m, "SoraAudioFrame") + .def("__getstate__", + [](const SoraAudioFrame& frame) { + return std::make_tuple( + frame.VectorData(), frame.samples_per_channel(), + frame.num_channels(), frame.sample_rate_hz()); + }) + .def("__setstate__", + [](SoraAudioFrame& frame, + const std::tuple, size_t, size_t, int>& + state) { + new (&frame) + SoraAudioFrame(std::get<0>(state), std::get<1>(state), + std::get<2>(state), std::get<3>(state)); + }) .def_prop_ro("samples_per_channel", &SoraAudioFrame::samples_per_channel) .def_prop_ro("num_channels", &SoraAudioFrame::num_channels) .def_prop_ro("sample_rate_hz", &SoraAudioFrame::sample_rate_hz) From 2bd461a5ef1c58b0452285c63803271eb6b40f3b Mon Sep 17 00:00:00 2001 From: tnoho Date: Tue, 25 Jul 2023 00:50:11 +0900 Subject: [PATCH 12/33] =?UTF-8?q?=E5=88=86=E5=B2=90=E3=82=92=E3=81=AA?= =?UTF-8?q?=E3=81=8F=E3=81=97=E3=81=A6=E3=82=AF=E3=83=A9=E3=82=B9=E3=82=92?= =?UTF-8?q?=E5=88=86=E3=81=91=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_sink2.cpp | 137 +++++++++++++++++++++++++++------------ src/sora_audio_sink2.h | 59 +++++++++++++++-- src/sora_sdk_ext.cpp | 13 ++-- 3 files changed, 156 insertions(+), 53 deletions(-) diff --git a/src/sora_audio_sink2.cpp b/src/sora_audio_sink2.cpp index 941acfa1..f8ade814 100644 --- a/src/sora_audio_sink2.cpp +++ b/src/sora_audio_sink2.cpp @@ -10,75 +10,130 @@ #include #include -SoraAudioFrame::SoraAudioFrame(std::unique_ptr audio_frame) +SoraAudioFrameDefaultImpl::SoraAudioFrameDefaultImpl( + std::unique_ptr audio_frame) : audio_frame_(std::move(audio_frame)) {} -SoraAudioFrame::SoraAudioFrame(std::vector vector, - size_t samples_per_channel, - size_t num_channels, - int sample_rate_hz) +const int16_t* SoraAudioFrameDefaultImpl::RawData() const { + return audio_frame_->data(); +} + +std::vector SoraAudioFrameDefaultImpl::VectorData() const { + std::vector vector( + audio_frame_->data(), + audio_frame_->data() + + audio_frame_->samples_per_channel() * audio_frame_->num_channels()); + return vector; +} + +size_t SoraAudioFrameDefaultImpl::samples_per_channel() const { + return audio_frame_->samples_per_channel(); +} + +size_t SoraAudioFrameDefaultImpl::num_channels() const { + return audio_frame_->num_channels(); +} + +int SoraAudioFrameDefaultImpl::sample_rate_hz() const { + return audio_frame_->sample_rate_hz(); +} + +std::optional +SoraAudioFrameDefaultImpl::absolute_capture_timestamp_ms() const { + if (audio_frame_->absolute_capture_timestamp_ms()) { + std::optional value = + *audio_frame_->absolute_capture_timestamp_ms(); + return value; + } else { + return std::nullopt; + } +} + +SoraAudioFrameVectorImpl::SoraAudioFrameVectorImpl( + std::vector vector, + size_t samples_per_channel, + size_t num_channels, + int sample_rate_hz, + std::optional absolute_capture_timestamp_ms) : vector_(vector), samples_per_channel_(samples_per_channel), num_channels_(num_channels), - sample_rate_hz_(sample_rate_hz) {} + sample_rate_hz_(sample_rate_hz), + absolute_capture_timestamp_ms_(absolute_capture_timestamp_ms) {} + +const int16_t* SoraAudioFrameVectorImpl::RawData() const { + return (const int16_t*)vector_.data(); +} + +std::vector SoraAudioFrameVectorImpl::VectorData() const { + return vector_; +} + +size_t SoraAudioFrameVectorImpl::samples_per_channel() const { + return samples_per_channel_; +} + +size_t SoraAudioFrameVectorImpl::num_channels() const { + return num_channels_; +} + +int SoraAudioFrameVectorImpl::sample_rate_hz() const { + return sample_rate_hz_; +} + +std::optional SoraAudioFrameVectorImpl::absolute_capture_timestamp_ms() + const { + // まだどう作るか考え中 + return absolute_capture_timestamp_ms_; +} + +SoraAudioFrame::SoraAudioFrame( + std::unique_ptr audio_frame) { + impl_.reset(new SoraAudioFrameDefaultImpl(std::move(audio_frame))); +} + +SoraAudioFrame::SoraAudioFrame( + std::vector vector, + size_t samples_per_channel, + size_t num_channels, + int sample_rate_hz, + std::optional absolute_capture_timestamp_ms) { + impl_.reset(new SoraAudioFrameVectorImpl(vector, samples_per_channel, + num_channels, sample_rate_hz, + absolute_capture_timestamp_ms)); +} nb::ndarray> SoraAudioFrame::Data() const { // Data はまだ vector の時は返せてない - size_t shape[2] = {static_cast(audio_frame_->samples_per_channel()), - static_cast(audio_frame_->num_channels())}; + size_t shape[2] = {static_cast(samples_per_channel()), + static_cast(num_channels())}; return nb::ndarray>( - (int16_t*)audio_frame_->data(), 2, shape); + (int16_t*)RawData(), 2, shape); } const int16_t* SoraAudioFrame::RawData() const { - if (audio_frame_) { - return audio_frame_->data(); - } else { - return (const int16_t*)vector_.data(); - } + return (const int16_t*)impl_->RawData(); } std::vector SoraAudioFrame::VectorData() const { - std::vector vector( - audio_frame_->data(), - audio_frame_->data() + - audio_frame_->samples_per_channel() * audio_frame_->num_channels()); - return vector; + return impl_->VectorData(); } size_t SoraAudioFrame::samples_per_channel() const { - if (audio_frame_) { - return audio_frame_->samples_per_channel(); - } else { - return samples_per_channel_; - } + return impl_->samples_per_channel(); } size_t SoraAudioFrame::num_channels() const { - if (audio_frame_) { - return audio_frame_->num_channels(); - } else { - return num_channels_; - } + return impl_->num_channels(); } int SoraAudioFrame::sample_rate_hz() const { - if (audio_frame_) { - return audio_frame_->sample_rate_hz(); - } else { - return sample_rate_hz_; - } + return impl_->sample_rate_hz(); } std::optional SoraAudioFrame::absolute_capture_timestamp_ms() const { - if (audio_frame_->absolute_capture_timestamp_ms()) { - std::optional value = - *audio_frame_->absolute_capture_timestamp_ms(); - return value; - } else { - return std::nullopt; - } + return impl_->absolute_capture_timestamp_ms(); } SoraAudioSink2Impl::SoraAudioSink2Impl(SoraTrackInterface* track, diff --git a/src/sora_audio_sink2.h b/src/sora_audio_sink2.h index 93d6a3ff..1b80b6e3 100644 --- a/src/sora_audio_sink2.h +++ b/src/sora_audio_sink2.h @@ -15,13 +15,64 @@ namespace nb = nanobind; +class SoraAudioFrameImpl { + public: + virtual ~SoraAudioFrameImpl() {} + virtual const int16_t* RawData() const = 0; + virtual std::vector VectorData() const = 0; + virtual size_t samples_per_channel() const = 0; + virtual size_t num_channels() const = 0; + virtual int sample_rate_hz() const = 0; + virtual std::optional absolute_capture_timestamp_ms() const = 0; +}; + +class SoraAudioFrameDefaultImpl : public SoraAudioFrameImpl { + public: + SoraAudioFrameDefaultImpl(std::unique_ptr audio_frame); + + const int16_t* RawData() const override; + std::vector VectorData() const override; + size_t samples_per_channel() const override; + size_t num_channels() const override; + int sample_rate_hz() const override; + std::optional absolute_capture_timestamp_ms() const override; + + private: + std::unique_ptr audio_frame_; +}; + +class SoraAudioFrameVectorImpl : public SoraAudioFrameImpl { + public: + SoraAudioFrameVectorImpl( + std::vector vector, + size_t samples_per_channel, + size_t num_channels, + int sample_rate_hz, + std::optional absolute_capture_timestamp_ms); + + const int16_t* RawData() const override; + std::vector VectorData() const override; + size_t samples_per_channel() const override; + size_t num_channels() const override; + int sample_rate_hz() const override; + std::optional absolute_capture_timestamp_ms() const override; + + private: + std::vector vector_; + size_t samples_per_channel_; + size_t num_channels_; + int sample_rate_hz_; + std::optional absolute_capture_timestamp_ms_; +}; + class SoraAudioFrame { public: SoraAudioFrame(std::unique_ptr audio_frame); SoraAudioFrame(std::vector vector, size_t samples_per_channel, size_t num_channels, - int sample_rate_hz); + int sample_rate_hz, + std::optional absolute_capture_timestamp_ms); nb::ndarray> Data() const; const int16_t* RawData() const; @@ -32,11 +83,7 @@ class SoraAudioFrame { std::optional absolute_capture_timestamp_ms() const; private: - std::unique_ptr audio_frame_; - std::vector vector_; - size_t samples_per_channel_; - size_t num_channels_; - int sample_rate_hz_; + std::unique_ptr impl_; }; class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index 1f4750a4..4db42fa0 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -211,15 +211,16 @@ NB_MODULE(sora_sdk_ext, m) { [](const SoraAudioFrame& frame) { return std::make_tuple( frame.VectorData(), frame.samples_per_channel(), - frame.num_channels(), frame.sample_rate_hz()); + frame.num_channels(), frame.sample_rate_hz(), + frame.absolute_capture_timestamp_ms()); }) .def("__setstate__", [](SoraAudioFrame& frame, - const std::tuple, size_t, size_t, int>& - state) { - new (&frame) - SoraAudioFrame(std::get<0>(state), std::get<1>(state), - std::get<2>(state), std::get<3>(state)); + const std::tuple, size_t, size_t, int, + std::optional>& state) { + new (&frame) SoraAudioFrame(std::get<0>(state), std::get<1>(state), + std::get<2>(state), std::get<3>(state), + std::get<4>(state)); }) .def_prop_ro("samples_per_channel", &SoraAudioFrame::samples_per_channel) .def_prop_ro("num_channels", &SoraAudioFrame::num_channels) From d286918752e3ae3d631040d6435598dcf2e0339f Mon Sep 17 00:00:00 2001 From: voluntas Date: Tue, 8 Aug 2023 11:25:27 +0900 Subject: [PATCH 13/33] =?UTF-8?q?C++=20SDK=20=E3=81=AE=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=92=E4=B8=8A=E3=81=92=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 9296b2ef..eaff2cd4 100644 --- a/VERSION +++ b/VERSION @@ -1,5 +1,5 @@ -SORA_CPP_SDK_VERSION=2023.7.2 -WEBRTC_BUILD_VERSION=m114.5735.2.0 +SORA_CPP_SDK_VERSION=2023.9.0 +WEBRTC_BUILD_VERSION=m115.5790.7.0 BOOST_VERSION=1.82.0 LYRA_VERSION=1.3.0 CMAKE_VERSION=3.26.4 From d0d2051417b64828b0932f4186088f309b94f53f Mon Sep 17 00:00:00 2001 From: voluntas Date: Tue, 8 Aug 2023 11:30:57 +0900 Subject: [PATCH 14/33] =?UTF-8?q?=E5=A4=89=E6=9B=B4=E5=B1=A5=E6=AD=B4?= =?UTF-8?q?=E3=82=92=E6=9B=B4=E6=96=B0=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index db350e19..33354e65 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,9 @@ ## develop +- [UPDATE] Sora C++ SDK のバージョンを 2023.9.0 に上げる + - @voluntas + ## 2023.3.1 **2023-07-13** From aafb4fb16a4dc08720e1a5c25c6694cc7067e554 Mon Sep 17 00:00:00 2001 From: tnoho Date: Sat, 26 Aug 2023 17:57:39 +0900 Subject: [PATCH 15/33] =?UTF-8?q?CreateConnection=20=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E5=AE=8C=E5=85=A8=E5=8C=96?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/sora.h b/src/sora.h index 9c6bf23d..11d98634 100644 --- a/src/sora.h +++ b/src/sora.h @@ -32,6 +32,49 @@ class Sora : public DisposePublisher { * 実装上の留意点:Sora C++ SDK が observer に std::weak_ptr を要求するためポインタで返す Source とは異なり、 * std::shared_ptr で返しますが Python での扱いは変わりません。 * + * @param signaling_urls シグナリングに使用する URL のリスト + * @param role ロール recvonly | sendonly | sendrecv + * @param channel_id チャネル ID + * @param client_id (オプション)クライアント ID + * @param bundle_id (オプション)バンドル ID + * @param metadata (オプション)認証メタデータ + * @param signaling_notify_metadata (オプション)シグナリング通知メタデータ + * @param audio_source (オプション)音声ソース CreateAudioSource で生成した SoraAudioSource を渡してください + * @param video_source (オプション)映像ソース CreateVideoSource で生成した SoraVideoSource を渡してください + * @param audio (オプション)音声の有効無効 デフォルト: true + * @param video (オプション)映像の有効無効 デフォルト: true + * @param audio_codec_type (オプション)音声コーデック OPUS | LYRA デフォルト: OPUS + * @param video_codec_type (オプション)映像コーデック VP8 | VP9 | AV1 | H264 デフォルト: VP9 + * @param video_bit_rate (オプション)映像ビットレート kbps 単位です + * @param audio_bit_rate (オプション)音声ビットレート kbps 単位です + * @param video_vp9_params (オプション)映像コーデック VP9 設定 + * @param video_av1_params (オプション)映像コーデック AV1 設定 + * @param video_h264_params (オプション)映像コーデック H264 設定 + * @param simulcast (オプション)サイマルキャストの有効無効 + * @param spotlight (オプション)スポットライトの有効無効 + * @param spotlight_number (オプション)スポットライトのフォーカス数 + * @param simulcast_rid (オプション)サイマルキャストで受信したい RID + * @param spotlight_focus_rid (オプション)スポットライトでフォーカスしているときのサイマルキャスト RID + * @param spotlight_unfocus_rid (オプション)スポットライトでフォーカスしていないときのサイマルキャスト RID + * @param forwarding_filter (オプション)転送フィルター設定 + * @param data_channels (オプション) DataChannel 設定 + * @param data_channel_signaling (オプション)シグナリングを DataChannel に切り替える機能の有効無効 + * @param ignore_disconnect_websocket (オプション)シグナリングを DataChannel に切り替えた際に WebSocket が切断されても切断としない機能の有効無効 + * @param data_channel_signaling_timeout (オプション) DataChannel シグナリングタイムアウト + * @param disconnect_wait_timeout (オプション) 切断待ちタイムアウト + * @param websocket_close_timeout (オプション) WebSocket クローズタイムアウト + * @param websocket_connection_timeout (オプション) WebSocket 接続タイムアウト + * @param audio_codec_lyra_bitrate (オプション) 音声コーデック Lyra のビットレート + * @param audio_codec_lyra_usedtx (オプション) 音声コーデック Lyra で DTX の有効無効 + * @param check_lyra_version (オプション) 音声コーデック Lyra のバージョンチェック有効無効 + * @param audio_streaming_language_code (オプション) 音声ストリーミング機能で利用する言語コード設定 + * @param insecure (オプション) 証明書チェックの有効無効 デフォルト: false + * @param client_cert (オプション) クライアント証明書 + * @param client_key (オプション) クライアントシークレットキー + * @param proxy_url (オプション) Proxy URL + * @param proxy_username (オプション) Proxy ユーザー名 + * @param proxy_password (オプション) Proxy パスワード + * @param proxy_agent (オプション) Proxy エージェント * @return SoraConnection インスタンス */ std::shared_ptr CreateConnection( From ddfc30d16fefbcce3f8ca96e4fa92dbe381c9d88 Mon Sep 17 00:00:00 2001 From: tnoho Date: Sat, 26 Aug 2023 20:34:08 +0900 Subject: [PATCH 16/33] =?UTF-8?q?sora.h=20=E3=82=92=E6=9B=B8=E3=81=8D?= =?UTF-8?q?=E8=B6=B3=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/sora.h b/src/sora.h index 11d98634..0f064c0d 100644 --- a/src/sora.h +++ b/src/sora.h @@ -22,6 +22,12 @@ */ class Sora : public DisposePublisher { public: + /** + * このタイミングで SoraFactory の生成まで行うため SoraFactory の生成にあたって必要な引数はここで設定します。 + * + * @param use_hardware_encoder (オプション)ハードウェアエンコーダーの有効無効 デフォルト: true + * @param openh264 (オプション) OpenH264 ライブラリへのパス + */ Sora(std::optional use_hardware_encoder, std::optional openh264); ~Sora(); @@ -150,6 +156,16 @@ class Sora : public DisposePublisher { SoraVideoSource* CreateVideoSource(); private: + /** + * Python で渡された値を boost::json::value に変換します。 + * + * metadata のように JSON の値として扱える内容であれば自由に指定できるものを、 + * nanobind::handle で受け取って Sora C++ SDK で使っている boost::json::value に変換します。 + * + * @param value Python から渡された値の nanobind::handle + * @param error_message 変換に失敗した際に nanobind::type_error で返す際のエラーメッセージ + * @return boost::json::value + */ boost::json::value ConvertJsonValue(nb::handle value, const char* error_message); std::vector ConvertDataChannels( From dbaf99472736d7eac02b4b2face63ec4f8b55bfe Mon Sep 17 00:00:00 2001 From: tnoho Date: Sat, 26 Aug 2023 20:57:46 +0900 Subject: [PATCH 17/33] =?UTF-8?q?SoraVideoSink=20=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_video_sink.cpp | 13 +++++++++++++ src/sora_video_sink.h | 11 +++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/sora_video_sink.cpp b/src/sora_video_sink.cpp index 7fc21d14..a2dc3f92 100644 --- a/src/sora_video_sink.cpp +++ b/src/sora_video_sink.cpp @@ -7,6 +7,12 @@ SoraVideoFrame::SoraVideoFrame( rtc::scoped_refptr i420_data) : width_(i420_data->width()), height_(i420_data->height()) { + /** + * データを取り出す際に Python 側で自由に FourCC を指定できる形にするのも手ですが、 + * その場合は関数を呼び出すたびに変換が走るので GIL を長く保持してしまいます。 + * また、複数回呼び出された際に毎回変換を行いパフォーマンスが悪化してしまうので、 + * ここで numpy の形式である 24BG に変換することとしました。 + */ argb_data_ = std::unique_ptr(new uint8_t[width_ * height_ * 3]); libyuv::ConvertFromI420( i420_data->DataY(), i420_data->StrideY(), i420_data->DataU(), @@ -27,6 +33,7 @@ SoraVideoSinkImpl::SoraVideoSinkImpl(SoraTrackInterface* track) track_->AddSubscriber(this); webrtc::VideoTrackInterface* video_track = static_cast(track_->GetTrack().get()); + // video_track にこの Sink を追加し OnFrame を呼び出してもらいます。 video_track->AddOrUpdateSink(this, rtc::VideoSinkWants()); } @@ -45,6 +52,7 @@ void SoraVideoSinkImpl::Disposed() { if (track_ && track_->GetTrack()) { webrtc::VideoTrackInterface* video_track = static_cast(track_->GetTrack().get()); + // video_track からこの Sink を削除します。 video_track->RemoveSink(this); } track_ = nullptr; @@ -58,6 +66,11 @@ void SoraVideoSinkImpl::OnFrame(const webrtc::VideoFrame& frame) { if (frame.width() == 0 || frame.height() == 0) return; if (on_frame_) { + /** + * 形式を問わず I420 でフレームデータを取得している。 + * 特殊なコーデックを選択しない限りはデコードされたフレームデータは I420 の形式になっているはずなので問題ないと考えた。 + * webrtc::VideoFrame を継承した特殊なフレームであったとしても ToI420 は実装されているはず。 + */ rtc::scoped_refptr i420_data = frame.video_frame_buffer()->ToI420(); on_frame_(std::make_shared(i420_data)); diff --git a/src/sora_video_sink.h b/src/sora_video_sink.h index e2f5c72b..fbb3ca2f 100644 --- a/src/sora_video_sink.h +++ b/src/sora_video_sink.h @@ -36,6 +36,7 @@ class SoraVideoFrame { nb::ndarray> Data(); private: + // width や height は ndarray に情報として含まれるため、これらを別で返す関数は不要 const int width_; const int height_; std::unique_ptr argb_data_; @@ -60,7 +61,13 @@ class SoraVideoSinkImpl : public rtc::VideoSinkInterface, void Del(); void Disposed(); - // rtc::VideoSinkInterface + /** + * VideoTrack からフレームデータが来るたびに呼び出される関数です。 + * + * 継承している rtc::VideoSinkInterface で定義されています。 + * + * @param frame VideoTrack から渡されるフレームデータ + */ void OnFrame(const webrtc::VideoFrame& frame) override; // DisposeSubscriber @@ -72,7 +79,7 @@ class SoraVideoSinkImpl : public rtc::VideoSinkInterface, * フレームが受信される度に呼び出されます。 * このコールバック関数内では重い処理は行わないでください。サンプルを参考に queue を利用するなどの対応を推奨します。 * また、この関数はメインスレッドから呼び出されないため、関数内で cv2.imshow を実行しても macOS の場合は表示されません。 - * 実装上の留意点:このコールバックで渡す引数は shared_ptr にしておかないとリークします + * 実装上の留意点:このコールバックで渡す引数は shared_ptr にしておかないとリークします。 */ std::function)> on_frame_; From 546a2c3db1a190684b85d526deeecee2bf958d3a Mon Sep 17 00:00:00 2001 From: voluntas Date: Sat, 26 Aug 2023 21:12:52 +0900 Subject: [PATCH 18/33] =?UTF-8?q?C++=20SDK=20=E3=82=92=202023.10.0=20?= =?UTF-8?q?=E3=81=AB=E4=B8=8A=E3=81=92=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 33354e65..333b3a32 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,7 +11,7 @@ ## develop -- [UPDATE] Sora C++ SDK のバージョンを 2023.9.0 に上げる +- [UPDATE] Sora C++ SDK のバージョンを 2023.10.0 に上げる - @voluntas ## 2023.3.1 diff --git a/VERSION b/VERSION index eaff2cd4..9637ddb2 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -SORA_CPP_SDK_VERSION=2023.9.0 +SORA_CPP_SDK_VERSION=2023.10.0 WEBRTC_BUILD_VERSION=m115.5790.7.0 BOOST_VERSION=1.82.0 LYRA_VERSION=1.3.0 From 824912133b49352a170bc36a8b1baa3386aefeb9 Mon Sep 17 00:00:00 2001 From: tnoho Date: Sat, 26 Aug 2023 23:05:09 +0900 Subject: [PATCH 19/33] =?UTF-8?q?=E5=AE=9F=E8=A3=85=E4=B8=8A=E3=81=AE?= =?UTF-8?q?=E7=95=99=E6=84=8F=E7=82=B9=E3=82=92=E8=BF=BD=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_factory.h | 3 +++ src/sora_sdk_ext.cpp | 20 ++++++++++++++++---- src/sora_track_interface.h | 22 +++++++++++++++++++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/sora_factory.h b/src/sora_factory.h index b637157b..34f0271e 100644 --- a/src/sora_factory.h +++ b/src/sora_factory.h @@ -11,6 +11,9 @@ // Sora #include +/** + * sora::SoraClientContext を呼び出す必要がある処理をまとめたクラスです。 + */ class SoraFactory { public: SoraFactory(std::optional use_hardware_encoder, diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index de7593a9..17f317bd 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -18,20 +18,25 @@ namespace nb = nanobind; using namespace nb::literals; -/* - * コールバック関数のメンバー変数は Py_tp_traverse で visit コールバックを呼び出すようにする - * やっておかないと終了時にリークエラーが発生する +/** + * クラスにコールバック関数のメンバー変数がある場合は全て以下のように、 + * Py_VISIT を呼び出すことによりガベージコレクタにその存在を伝える関数を作る。 + * やっておかないと終了時にリークエラーが発生する。 */ - int audio_sink_tp_traverse(PyObject* self, visitproc visit, void* arg) { + // インスタンスを取得する SoraAudioSinkImpl* audio_sink = nb::inst_ptr(self); + // コールバックがある場合 if (audio_sink->on_format_) { + // コールバック変数の参照を取得して nb::object on_format = nb::cast(audio_sink->on_format_, nb::rv_policy::none); + // ガベージコレクタに伝える Py_VISIT(on_format.ptr()); } + // 上に同じ if (audio_sink->on_data_) { nb::object on_data = nb::cast(audio_sink->on_data_, nb::rv_policy::none); Py_VISIT(on_data.ptr()); @@ -40,6 +45,10 @@ int audio_sink_tp_traverse(PyObject* self, visitproc visit, void* arg) { return 0; } +/** + * PyType_Slot の Py_tp_traverse に先に作った関数を設定する。 + * 定義した PyType_Slot は NB_MODULE 内の対応するクラスに対して紐づける。 + */ PyType_Slot audio_sink_slots[] = { {Py_tp_traverse, (void*)audio_sink_tp_traverse}, {0, nullptr}}; @@ -107,6 +116,9 @@ PyType_Slot connection_slots[] = { {Py_tp_traverse, (void*)connection_tp_traverse}, {0, nullptr}}; +/** + * Python で利用するすべてのクラスと定数は以下のように定義しなければならない + */ NB_MODULE(sora_sdk_ext, m) { nb::enum_(m, "SoraSignalingErrorCode", nb::is_arithmetic()) diff --git a/src/sora_track_interface.h b/src/sora_track_interface.h index 531a992e..18e33f9c 100644 --- a/src/sora_track_interface.h +++ b/src/sora_track_interface.h @@ -7,6 +7,12 @@ #include "dispose_listener.h" +/** + * webrtc::MediaStreamTrackInterface を格納する SoraTrackInterface です。 + * + * webrtc::MediaStreamTrackInterface は rtc::scoped_refptr のため、 + * nanobind で直接のハンドリングが難しいために用意しました。 + */ class SoraTrackInterface : public DisposePublisher, public DisposeSubscriber { public: SoraTrackInterface( @@ -20,6 +26,11 @@ class SoraTrackInterface : public DisposePublisher, public DisposeSubscriber { Disposed(); } + /** + * Python で呼び出すための関数 + * この実装では track_ が nullptr になっているとクラッシュするが、 + * その時には publisher_ も失われているため許容することとした。 + */ std::string kind() const { return track_->kind(); } std::string id() const { return track_->id(); } bool enabled() const { return track_->enabled(); } @@ -27,6 +38,12 @@ class SoraTrackInterface : public DisposePublisher, public DisposeSubscriber { webrtc::MediaStreamTrackInterface::TrackState state() { return track_->state(); } + + /** + * webrtc::MediaStreamTrackInterface の実体を取り出すため Python SDK 内で使う関数 + * + * @return rtc::scoped_refptr + */ rtc::scoped_refptr GetTrack() { return track_; } @@ -36,7 +53,10 @@ class SoraTrackInterface : public DisposePublisher, public DisposeSubscriber { publisher_ = nullptr; track_ = nullptr; } - virtual void PublisherDisposed() override { Disposed(); } + virtual void PublisherDisposed() override { + // Track は生成元が破棄された後に再利用することはないので Disposed() を呼ぶ + Disposed(); + } protected: DisposePublisher* publisher_; From 94b63ad311d2622a247e6df4f2f9d4aae869bfc1 Mon Sep 17 00:00:00 2001 From: tnoho Date: Sun, 27 Aug 2023 00:32:29 +0900 Subject: [PATCH 20/33] =?UTF-8?q?=E6=96=87=E4=BD=93=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_track_interface.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sora_track_interface.h b/src/sora_track_interface.h index 18e33f9c..ec642ceb 100644 --- a/src/sora_track_interface.h +++ b/src/sora_track_interface.h @@ -10,8 +10,8 @@ /** * webrtc::MediaStreamTrackInterface を格納する SoraTrackInterface です。 * - * webrtc::MediaStreamTrackInterface は rtc::scoped_refptr のため、 - * nanobind で直接のハンドリングが難しいために用意しました。 + * webrtc::MediaStreamTrackInterface は rtc::scoped_refptr なので、 + * nanobind で直接のハンドリングが難しいので用意しました。 */ class SoraTrackInterface : public DisposePublisher, public DisposeSubscriber { public: @@ -28,8 +28,8 @@ class SoraTrackInterface : public DisposePublisher, public DisposeSubscriber { /** * Python で呼び出すための関数 - * この実装では track_ が nullptr になっているとクラッシュするが、 - * その時には publisher_ も失われているため許容することとした。 + * この実装では track_ が nullptr になっているとクラッシュしてしまいますが、 + * その時には publisher_ も失われているため許容することとしました。 */ std::string kind() const { return track_->kind(); } std::string id() const { return track_->id(); } @@ -40,7 +40,7 @@ class SoraTrackInterface : public DisposePublisher, public DisposeSubscriber { } /** - * webrtc::MediaStreamTrackInterface の実体を取り出すため Python SDK 内で使う関数 + * webrtc::MediaStreamTrackInterface の実体を取り出すため Python SDK 内で使う関数です。 * * @return rtc::scoped_refptr */ From c6c58374f3d0c7cec244940adb0526d0495c8d02 Mon Sep 17 00:00:00 2001 From: tnoho Date: Sun, 27 Aug 2023 00:33:29 +0900 Subject: [PATCH 21/33] =?UTF-8?q?SoraConnection=20=E3=81=AE=E8=AA=AC?= =?UTF-8?q?=E6=98=8E=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_connection.cpp | 11 +++++++-- src/sora_connection.h | 51 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/sora_connection.cpp b/src/sora_connection.cpp index 93606b9b..eef52d6e 100644 --- a/src/sora_connection.cpp +++ b/src/sora_connection.cpp @@ -34,6 +34,7 @@ void SoraConnection::PublisherDisposed() { } void SoraConnection::Init(sora::SoraSignalingConfig& config) { + // TODO(tnoho): 複数回の呼び出しは禁止なので、ちゃんと throw する ioc_.reset(new boost::asio::io_context(1)); config.io_context = ioc_.get(); conn_ = sora::SoraSignaling::Create(config); @@ -51,6 +52,7 @@ void SoraConnection::Connect() { conn_->Connect(); + // ioc_->run(); は別スレッドで呼ばなければ、この関数は切断されるまで返らなくなってしまう thread_.reset(new std::thread([this]() { auto guard = boost::asio::make_work_guard(*ioc_); ioc_->run(); @@ -76,6 +78,7 @@ void SoraConnection::Disconnect() { } void SoraConnection::SetAudioTrack(SoraTrackInterface* audio_source) { + // TODO(tnoho): audio_sender_ がないと意味がないので、エラーを返すようにするべき if (audio_sender_) { audio_sender_->SetTrack(audio_source->GetTrack().get()); } @@ -87,6 +90,7 @@ void SoraConnection::SetAudioTrack(SoraTrackInterface* audio_source) { } void SoraConnection::SetVideoTrack(SoraTrackInterface* video_source) { + // TODO(tnoho): video_sender_ がないと意味がないので、エラーを返すようにするべき if (video_sender_) { video_sender_->SetTrack(video_source->GetTrack().get()); } @@ -109,6 +113,7 @@ void SoraConnection::OnSetOffer(std::string offer) { audio_result = conn_->GetPeerConnection()->AddTrack( audio_source_->GetTrack(), {stream_id}); if (audio_result.ok()) { + // javascript でいう replaceTrack を実装するために webrtc::RtpSenderInterface の参照をとっておく audio_sender_ = audio_result.value(); } } @@ -154,7 +159,7 @@ void SoraConnection::OnMessage(std::string label, std::string data) { void SoraConnection::OnTrack( rtc::scoped_refptr transceiver) { if (on_track_) { - // shared_ptr になってないのでリークする + // shared_ptr になってないとリークする auto track = std::make_shared( this, transceiver->receiver()->track()); AddSubscriber(track.get()); @@ -163,7 +168,9 @@ void SoraConnection::OnTrack( } void SoraConnection::OnRemoveTrack( - rtc::scoped_refptr receiver) {} + rtc::scoped_refptr receiver) { + // TODO(tnoho): 要実装 +} void SoraConnection::OnDataChannel(std::string label) { if (on_data_channel_) { diff --git a/src/sora_connection.h b/src/sora_connection.h index 50309811..0008789c 100644 --- a/src/sora_connection.h +++ b/src/sora_connection.h @@ -23,24 +23,70 @@ namespace nb = nanobind; +/** + * Sora との接続ごとに生成する SoraConnection です。 + * + * Python に Connection を制御する関数を提供します。 + */ class SoraConnection : public sora::SoraSignalingObserver, public DisposePublisher, public DisposeSubscriber { public: + /** + * コンストラクタではインスタンスの生成のみで実際の生成処理は Init 関数で行います。 + */ SoraConnection(DisposePublisher* publisher); ~SoraConnection(); void Disposed() override; void PublisherDisposed() override; + /** + * SoraConnection の初期化を行う関数です。 + * + * この関数は現在記述されている 1 箇所以外での呼び出しは禁止です。 + * 実際に Sora との接続である sora::SoraSignaling を生成しているのはこの関数です。 + * Python から Connection に各種コールバックを容易に設定できるようにするために、 + * SoraConnection に sora::SoraSignalingObserver を継承させました。 + * しかし sora::SoraSignalingConfig::observer が sora::SoraSignalingObserver の弱参照を要求するので、 + * SoraConnection 生成時には何もせず、ここで sora::SoraSignalingConfig を受け取って初期化するようにしました。 + * + * @param config Sora への接続設定を持つ sora::SoraSignalingConfig + */ void Init(sora::SoraSignalingConfig& config); + /** + * Sora と接続する関数です。 + */ void Connect(); + /** + * Sora から切断する関数です。 + */ void Disconnect(); + /** + * 音声トラックを入れ替える javascript でいう replaceTrack に相当する関数です。 + * + * TODO(tnoho): Python で呼び出すことを想定しているが、動作確認していないため NB_MODULE に定義していない + * + * @param audio_source 入れ替える新しい音声トラック + */ void SetAudioTrack(SoraTrackInterface* audio_source); + /** + * 映像トラックを入れ替える javascript でいう replaceTrack に相当する関数です。 + * + * TODO(tnoho): Python で呼び出すことを想定しているが、動作確認していないため NB_MODULE に定義していない + * + * @param audio_source 入れ替える新しい映像トラック + */ void SetVideoTrack(SoraTrackInterface* video_source); + /** + * DataChannel でデータを送信する関数です。 + * + * @param label 送信する DataChannel の label + * @param data 送信するデータ + */ bool SendDataChannel(const std::string& label, nb::bytes& data); - // sora::SoraSignalingObserver + // sora::SoraSignalingObserver に定義されているコールバック関数 void OnSetOffer(std::string offer) override; void OnDisconnect(sora::SoraSignalingErrorCode ec, std::string message) override; @@ -53,6 +99,7 @@ class SoraConnection : public sora::SoraSignalingObserver, rtc::scoped_refptr receiver) override; void OnDataChannel(std::string label) override; + // sora::SoraSignalingObserver のコールバック関数が呼び出された時に対応して呼び出す Python の関数を保持する std::function on_set_offer_; std::function on_disconnect_; std::function on_notify_; @@ -66,8 +113,10 @@ class SoraConnection : public sora::SoraSignalingObserver, std::unique_ptr ioc_; std::shared_ptr conn_; std::unique_ptr thread_; + // javascript でいう replaceTrack された際に RemoveSubscriber を呼び出すために参照を保持する SoraTrackInterface* audio_source_ = nullptr; SoraTrackInterface* video_source_ = nullptr; + // javascript でいう replaceTrack を実装するために webrtc::RtpSenderInterface の参照を保持する rtc::scoped_refptr audio_sender_; rtc::scoped_refptr video_sender_; }; From 83279e4351326269e28d3f67892983bdf1bf159c Mon Sep 17 00:00:00 2001 From: tnoho Date: Sun, 27 Aug 2023 01:07:37 +0900 Subject: [PATCH 22/33] =?UTF-8?q?DisposeSubscriber,=20DisposePublisher=20?= =?UTF-8?q?=E3=81=AB=E3=81=A4=E3=81=84=E3=81=A6=E8=BF=BD=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dispose_listener.h | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/dispose_listener.h b/src/dispose_listener.h index 4b6780d2..91eb0c2e 100644 --- a/src/dispose_listener.h +++ b/src/dispose_listener.h @@ -3,16 +3,48 @@ #include +/** + * 実装上の留意点:Sora Python SDK は Sora, AudioSource, VideoSource, Connection, Track など、 + * それぞれを Python で別々で扱うことができるようになっているが実態としては親を破棄すると子が止まる関係性が存在する。 + * これを適切にハンドリングしなければリークを引き起こしてしまうため、破棄された、もしくはされることを通知して、 + * 適切にハンドリングを行うためのクラス DisposeSubscriber, DisposePublisher を用意した。 + */ + +/** + * 破棄された通知を受ける DisposeSubscriber です。 + * + * これを継承することで、 DisposePublisher から破棄された通知を受け取ることができます。 + */ class DisposeSubscriber { public: + /** + * Subscribe している Publisher が破棄された際に呼び出される関数です。 + */ virtual void PublisherDisposed() = 0; }; +/** + * 破棄された際に DisposeSubscriber に通知を送る DisposePublisher です。 + * + * 継承して使うことを想定しています。 1 つのインスタンスで複数ので DisposePublisher に破棄を通知することができます。 + */ class DisposePublisher { public: + // クラスによって、 Disposed を呼ぶタイミングを調整する必要があるためデストラクタでの一律 Disposed 呼び出しは行わない + + /** + * Subscribe する際に呼ぶ関数です。 + * + * @param subscriber Subscribe する DisposeSubscriber + */ void AddSubscriber(DisposeSubscriber* subscriber) { subscribers_.push_back(subscriber); } + /** + * Subscribe を解除する際に呼ぶ関数です。 + * + * @param subscriber Subscribe を解除する DisposeSubscriber + */ void RemoveSubscriber(DisposeSubscriber* subscriber) { subscribers_.erase( std::remove_if(subscribers_.begin(), subscribers_.end(), @@ -21,6 +53,11 @@ class DisposePublisher { }), subscribers_.end()); } + /** + * Subscriber に破棄されたことを通知する際に呼ぶ関数です。 + * + * TODO(tnoho): 役割的に protected にして良いのでは。 + */ virtual void Disposed() { for (DisposeSubscriber* subscriber : subscribers_) { subscriber->PublisherDisposed(); From e4863cf3ae6d188433d9c01edb1a719e660e684c Mon Sep 17 00:00:00 2001 From: tnoho Date: Sun, 27 Aug 2023 01:32:58 +0900 Subject: [PATCH 23/33] =?UTF-8?q?DummyAudioMixer=20=E3=81=AB=E3=81=A4?= =?UTF-8?q?=E3=81=84=E3=81=A6=E8=BF=BD=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dummy_audio_mixer.cpp | 15 +++++++++++++-- src/dummy_audio_mixer.h | 12 ++++++++++++ src/sora_factory.cpp | 3 +++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/dummy_audio_mixer.cpp b/src/dummy_audio_mixer.cpp index b7c69bdb..d8413fd8 100644 --- a/src/dummy_audio_mixer.cpp +++ b/src/dummy_audio_mixer.cpp @@ -16,8 +16,14 @@ void DummyAudioMixer::Mix(size_t number_of_channels, webrtc::AudioFrame* audio_frame_for_mixing) { webrtc::MutexLock lock(&mutex_); for (auto& source_and_status : audio_source_list_) { - // 第一引数の設定値にサンプリングレートがリサンプリングされる - // -1 を指定するとリサンプリングされなくなる + /** + * webrtc::AudioTrackSinkInterface の OnData はこの関数内で呼ばれる + * + * 第一引数の設定値にサンプリングレートがリサンプリングされるが、 + * -1 を指定するとリサンプリングされなくなる。 + * SoraAudioSinkImpl の OnData 内でリサンプリングするため、 + * ここでは -1 を指定している。 + */ source_and_status->audio_source->GetAudioFrameWithInfo( -1, &source_and_status->audio_frame); } @@ -41,6 +47,11 @@ void DummyAudioMixer::RemoveSource(Source* audio_source) { DummyAudioMixer::DummyAudioMixer(webrtc::TaskQueueFactory* task_queue_factory) : task_queue_factory_(task_queue_factory) { + /** + * 通常 webrtc::AudioMixer の Mix は音声出力デバイスのループで呼ばれるが、 + * sora::SoraClientContextConfig::use_audio_device を false にした際に設定される、 + * webrtc::AudioDeviceDummy はループを回さないため、ここでループを作ることとした。 + */ task_queue_ = std::make_unique(task_queue_factory_->CreateTaskQueue( "TestAudioDeviceModuleImpl", diff --git a/src/dummy_audio_mixer.h b/src/dummy_audio_mixer.h index b3bc2b05..6d2e0b0d 100644 --- a/src/dummy_audio_mixer.h +++ b/src/dummy_audio_mixer.h @@ -12,6 +12,18 @@ #include #include +/** + * webrtc::AudioMixer を継承した DummyAudioMixer です。 + * + * PeerConnectionFactory 生成時に渡す cricket::MediaEngineDependencies の + * audio_mixer を指定しない場合 webrtc::AudioMixerImpl が使用されます。 + * これはすべての AudioTrack の出力データのサンプリングレートとチャネル数を揃え、 + * ミキシングした上で音声出力デバイスに渡す役割を担います。 + * しかし、 Python SDK では音声をデバイスに出力することはありません。 + * ですが、 AudioTrack からデータを受け取る AudioSinkInterface::OnData は + * AudioMixer により駆動されているため、 AudioSinkInterface::OnData を呼び出す仕組みだけを持つ + * シンプルな webrtc::AudioMixer になっています。 + */ class DummyAudioMixer : public webrtc::AudioMixer { public: struct SourceStatus; diff --git a/src/sora_factory.cpp b/src/sora_factory.cpp index 4d4d0ab4..0a64829c 100644 --- a/src/sora_factory.cpp +++ b/src/sora_factory.cpp @@ -36,6 +36,7 @@ SoraFactory::SoraFactory(std::optional use_hardware_encoder, #endif sora::SoraClientContextConfig context_config; + // Audio デバイスは使わない、 use_audio_device を true にしただけでデバイスを掴んでしまうので常に false context_config.use_audio_device = false; if (use_hardware_encoder) { context_config.use_hardware_encoder = *use_hardware_encoder; @@ -44,8 +45,10 @@ SoraFactory::SoraFactory(std::optional use_hardware_encoder, [use_hardware_encoder = context_config.use_hardware_encoder, openh264]( const webrtc::PeerConnectionFactoryDependencies& dependencies, cricket::MediaEngineDependencies& media_dependencies) { + // 通常の AudioMixer を使うと use_audio_device が false のとき、音声のループは全て止まってしまうので自前の AudioMixer を使う media_dependencies.audio_mixer = DummyAudioMixer::Create(media_dependencies.task_queue_factory); + // アンチエコーやゲインコントロール、ノイズサプレッションが必要になる用途は想定していないため nullptr media_dependencies.audio_processing = nullptr; #ifndef _WIN32 From a80d4d1e0dc32cf51b2b3e3921b7344164820fc5 Mon Sep 17 00:00:00 2001 From: tnoho Date: Sun, 27 Aug 2023 12:45:27 +0900 Subject: [PATCH 24/33] =?UTF-8?q?SoraAudioSink2=20=E3=81=AB=E3=82=B3?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_sink2.cpp | 1 - src/sora_audio_sink2.h | 87 ++++++++++++++++++++++++++++++++++++++-- src/sora_sdk_ext.cpp | 2 + 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/sora_audio_sink2.cpp b/src/sora_audio_sink2.cpp index f8ade814..4eca2d6a 100644 --- a/src/sora_audio_sink2.cpp +++ b/src/sora_audio_sink2.cpp @@ -83,7 +83,6 @@ int SoraAudioFrameVectorImpl::sample_rate_hz() const { std::optional SoraAudioFrameVectorImpl::absolute_capture_timestamp_ms() const { - // まだどう作るか考え中 return absolute_capture_timestamp_ms_; } diff --git a/src/sora_audio_sink2.h b/src/sora_audio_sink2.h index 1b80b6e3..d72d1c5d 100644 --- a/src/sora_audio_sink2.h +++ b/src/sora_audio_sink2.h @@ -15,6 +15,9 @@ namespace nb = nanobind; +/** + * SoraAudioFrame 内で音声データを持つクラスの抽象クラス + */ class SoraAudioFrameImpl { public: virtual ~SoraAudioFrameImpl() {} @@ -26,6 +29,11 @@ class SoraAudioFrameImpl { virtual std::optional absolute_capture_timestamp_ms() const = 0; }; +/** + * SoraAudioFrame を SoraAudioSink2Impl から生成した際にデータを持つクラスです。 + * + * libwebrtc でオーディオデータを扱う際の単位である webrtc::AudioFrame のまま扱います。 + */ class SoraAudioFrameDefaultImpl : public SoraAudioFrameImpl { public: SoraAudioFrameDefaultImpl(std::unique_ptr audio_frame); @@ -41,6 +49,11 @@ class SoraAudioFrameDefaultImpl : public SoraAudioFrameImpl { std::unique_ptr audio_frame_; }; +/** + * SoraAudioFrame を pickle した状態から __setstate__ で戻した場合にデータを持つクラスです。 + * + * nanobind でハンドリングできる型のみでコンストラクタを構成しています。 + */ class SoraAudioFrameVectorImpl : public SoraAudioFrameImpl { public: SoraAudioFrameVectorImpl( @@ -65,27 +78,89 @@ class SoraAudioFrameVectorImpl : public SoraAudioFrameImpl { std::optional absolute_capture_timestamp_ms_; }; +/** + * 受信した 10ms 単位の音声データを保持する SoraAudioFrame です。 + * + * SoraAudioSink2Impl から生成するための webrtc::AudioFrame を引数にもつコンストラクタと + * pickle に対応するための Python から生成ためのコンストラクタが存在します。 + * それぞれでデータの持ち方が異なるため実際のデータは SoraAudioFrameImpl の impl_ 内にもっていて、 + * このクラスは Python へのインターフェイスを提供します。 + * pickle に対応するために抽象クラスだけでなく、このようなクラスが必要になりました。 + */ class SoraAudioFrame { public: + // SoraAudioSink2Impl から生成する際のコンストラクタ SoraAudioFrame(std::unique_ptr audio_frame); + // pickle した状態から __setstate__ で戻す際に使うコンストラクタ SoraAudioFrame(std::vector vector, size_t samples_per_channel, size_t num_channels, int sample_rate_hz, std::optional absolute_capture_timestamp_ms); - + /** + * SoraAudioFrame 内の音声データへの numpy.ndarray での参照を返します。 + * + * @return NumPy の配列 numpy.ndarray で サンプル数 x チャンネル数 になっている音声データ + */ nb::ndarray> Data() const; + /** + * SoraAudioFrame 内の音声データへの直接参照を返します。 + * + * Python SDK 内で使う関数です。 + * + * @return 音声データの int16_t* ポインタ + */ const int16_t* RawData() const; + /** + * SoraAudioFrame 内の音声データを std::vector で返します。 + * + * Python SDK 内で使う関数で pickle 化するために使います。 + * + * @return 音声データの std::vector + */ std::vector VectorData() const; + /** + * チャネルあたりのサンプル数を返します。 + * + * @return チャネルあたりのサンプル数 + */ size_t samples_per_channel() const; + /** + * チャネル数を返します。 + * + * @return チャネル数 + */ size_t num_channels() const; + /** + * サンプリングレートを返します。 + * + * @return サンプリングレート + */ int sample_rate_hz() const; + /** + * キャプチャした際のタイムスタンプがあればミリ秒で返します。 + * + * @return キャプチャした際のタイムスタンプ + */ std::optional absolute_capture_timestamp_ms() const; private: std::unique_ptr impl_; }; +/** + * Sora からの音声を受け取る SoraAudioSink2Impl です。 + * + * Connection の OnTrack コールバックから渡されるリモート Track から音声を取り出すことができます。 + * Track からの音声はコンストラクタで設定したサンプリングレートとチャネル数に変換し、 + * SoraAudioFrame に格納した上でコールバックで Python に音声データを渡します。 + * コールバックは libwebrtc 内部での扱いから 10ms 間隔で呼び出されるため、 + * コールバックは速やかに処理を返すことが求められます。 10ms 単位の高いリアルタム性を求めないのであれば、 + * 内部にバッファを持ち任意のタイミングで音声を取り出すことができる SoraAudioSink の利用を推奨します。 + * + * 実装上の留意点:Track の参照保持のための Impl のない SoraAudioSink2 を __init__.py に定義しています。 + * SoraAudioSink2Impl を直接 Python から呼び出すことは想定していません。 + */ class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, public DisposeSubscriber { public: @@ -104,8 +179,14 @@ class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, size_t number_of_channels, size_t number_of_frames, absl::optional absolute_capture_timestamp_ms) override; - - // このコールバックは shared_ptr にしないとリークする + /** + * 音声データが来るたびに呼び出されるコールバック変数です。 + * + * Track から音声データが渡される 10ms 間隔で呼び出されます。このコールバック関数内では重い処理は行わないでください。 + * 渡される SoraAudioFrame は pickle が利用可能のため別プロセスなどにオフロードすることを推奨します。 + * また、この関数はメインスレッドから呼び出されないため留意してください。 + * 実装上の留意点:このコールバックで渡す引数は shared_ptr にしておかないとリークします。 + */ std::function)> on_frame_; private: diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index 4db42fa0..6c9ecf67 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -209,6 +209,7 @@ NB_MODULE(sora_sdk_ext, m) { nb::class_(m, "SoraAudioFrame") .def("__getstate__", [](const SoraAudioFrame& frame) { + // picke 化する際に呼び出されるので、すべてのデータを tuple に格納します。 return std::make_tuple( frame.VectorData(), frame.samples_per_channel(), frame.num_channels(), frame.sample_rate_hz(), @@ -218,6 +219,7 @@ NB_MODULE(sora_sdk_ext, m) { [](SoraAudioFrame& frame, const std::tuple, size_t, size_t, int, std::optional>& state) { + // picke から戻す際に呼び出されるので、 tuple から SoraAudioFrame に戻します。 new (&frame) SoraAudioFrame(std::get<0>(state), std::get<1>(state), std::get<2>(state), std::get<3>(state), std::get<4>(state)); From e9832b98565584e239ebfd10d015897fe7d57725 Mon Sep 17 00:00:00 2001 From: tnoho Date: Sun, 27 Aug 2023 13:17:31 +0900 Subject: [PATCH 25/33] =?UTF-8?q?VAD=20=E3=81=AB=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_vad.cpp | 1 + src/sora_vad.h | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/sora_vad.cpp b/src/sora_vad.cpp index 99c36729..44df8a2d 100644 --- a/src/sora_vad.cpp +++ b/src/sora_vad.cpp @@ -23,6 +23,7 @@ float SoraVAD::Analyze(std::shared_ptr frame) { if (!audio_buffer_ || vad_input_config_.sample_rate_hz() != frame->sample_rate_hz() || vad_input_config_.num_channels() != frame->num_channels()) { + // audio_buffer_ のサンプリングレートやチャネル数と frame のそれが一致しない場合は audio_buffer_ を初期化する audio_buffer_.reset(new webrtc::AudioBuffer( frame->sample_rate_hz(), frame->num_channels(), webrtc::rnn_vad::kSampleRate24kHz, // VAD は 24kHz なので合わせる diff --git a/src/sora_vad.h b/src/sora_vad.h index eea3e18b..fd4d7bc1 100644 --- a/src/sora_vad.h +++ b/src/sora_vad.h @@ -15,10 +15,26 @@ namespace nb = nanobind; +/** + * SoraAudioFrame の音声データに音声である率を返す VAD です。 + * + * 受信した音声データに対して何らかの処理を Python で行う場合、 + * 全体の負荷軽減を考えるのであれば音声データから音声であると推測されるデータのみに、 + * 処理を行うようにした方が全体の負荷を下げることができます。 + * libwebrtc には優秀な VAD が含まれているため、これを活用したユーティリティクラスとして用意しました。 + */ class SoraVAD { public: SoraVAD(); + /** + * SoraAudioFrame 内の音声データが音声である確率を返します。 + * + * libwebrtc 内部では 0.95 より大きい場合に音声とみなしています。 + * + * @param frame 音声である確率を求める SoraAudioFrame + * @return 0 - 1 で表される音声である確率 + */ float Analyze(std::shared_ptr frame); private: From a3d0c0857fa22688c8207cf7af6e38e898ed3e7e Mon Sep 17 00:00:00 2001 From: tnoho Date: Sun, 27 Aug 2023 13:20:42 +0900 Subject: [PATCH 26/33] =?UTF-8?q?PR=20=E3=82=92=E5=88=86=E3=81=91=E3=82=8B?= =?UTF-8?q?=E3=81=9F=E3=82=81=E3=80=81=20VAD=20=E3=82=92=E4=B8=80=E6=97=A6?= =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 - src/sora_sdk_ext.cpp | 5 ----- src/sora_vad.cpp | 42 ---------------------------------------- src/sora_vad.h | 46 -------------------------------------------- 4 files changed, 94 deletions(-) delete mode 100644 src/sora_vad.cpp delete mode 100644 src/sora_vad.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f819036..35bebf97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,6 @@ nanobind_add_module( src/sora_factory.cpp src/sora_log.cpp src/sora_sdk_ext.cpp - src/sora_vad.cpp src/sora_video_sink.cpp src/sora_video_source.cpp ) diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index 6c9ecf67..0139463e 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -15,7 +15,6 @@ #include "sora_connection.h" #include "sora_log.h" #include "sora_track_interface.h" -#include "sora_vad.h" #include "sora_video_sink.h" #include "sora_video_source.h" @@ -238,10 +237,6 @@ NB_MODULE(sora_sdk_ext, m) { .def("__del__", &SoraAudioSink2Impl::Del) .def_rw("on_frame", &SoraAudioSink2Impl::on_frame_); - nb::class_(m, "SoraVAD") - .def(nb::init<>()) - .def("analyze", &SoraVAD::Analyze); - nb::class_(m, "SoraVideoFrame") .def("data", &SoraVideoFrame::Data, nb::rv_policy::reference); diff --git a/src/sora_vad.cpp b/src/sora_vad.cpp deleted file mode 100644 index 44df8a2d..00000000 --- a/src/sora_vad.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "sora_vad.h" - -#include - -// WebRTC -#include -#include -#include -#include -#include -#include - -SoraVAD::SoraVAD() { - vad_ = std::make_unique( - webrtc::kVadResetPeriodMs, // libWebRTC 内部の設定に合わせる - webrtc::GetAvailableCpuFeatures(), - webrtc::rnn_vad:: - kSampleRate24kHz // 24kHz にしておかないと、VAD 前にリサンプリングが走る - ); -} - -float SoraVAD::Analyze(std::shared_ptr frame) { - if (!audio_buffer_ || - vad_input_config_.sample_rate_hz() != frame->sample_rate_hz() || - vad_input_config_.num_channels() != frame->num_channels()) { - // audio_buffer_ のサンプリングレートやチャネル数と frame のそれが一致しない場合は audio_buffer_ を初期化する - audio_buffer_.reset(new webrtc::AudioBuffer( - frame->sample_rate_hz(), frame->num_channels(), - webrtc::rnn_vad::kSampleRate24kHz, // VAD は 24kHz なので合わせる - 1, // VAD は 1 チャンネルなので合わせる - webrtc::rnn_vad:: - kSampleRate24kHz, // 出力はしないが、余計なインスタンスを生成しないよう合わせる - 1 // 出力はしないが VAD とチャネル数は合わせておく - )); - vad_input_config_ = - webrtc::StreamConfig(frame->sample_rate_hz(), frame->num_channels()); - } - audio_buffer_->CopyFrom(frame->RawData(), vad_input_config_); - return vad_->Analyze(webrtc::AudioFrameView( - audio_buffer_->channels(), audio_buffer_->num_channels(), - audio_buffer_->num_frames())); -} diff --git a/src/sora_vad.h b/src/sora_vad.h deleted file mode 100644 index fd4d7bc1..00000000 --- a/src/sora_vad.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef SORA_VAD_H_ -#define SORA_VAD_H_ - -// nonobind -#include -#include - -// WebRTC -#include -#include -#include -#include - -#include "sora_audio_sink2.h" - -namespace nb = nanobind; - -/** - * SoraAudioFrame の音声データに音声である率を返す VAD です。 - * - * 受信した音声データに対して何らかの処理を Python で行う場合、 - * 全体の負荷軽減を考えるのであれば音声データから音声であると推測されるデータのみに、 - * 処理を行うようにした方が全体の負荷を下げることができます。 - * libwebrtc には優秀な VAD が含まれているため、これを活用したユーティリティクラスとして用意しました。 - */ -class SoraVAD { - public: - SoraVAD(); - - /** - * SoraAudioFrame 内の音声データが音声である確率を返します。 - * - * libwebrtc 内部では 0.95 より大きい場合に音声とみなしています。 - * - * @param frame 音声である確率を求める SoraAudioFrame - * @return 0 - 1 で表される音声である確率 - */ - float Analyze(std::shared_ptr frame); - - private: - std::unique_ptr audio_buffer_; - webrtc::StreamConfig vad_input_config_; - std::unique_ptr vad_; -}; - -#endif \ No newline at end of file From 6e536bffbbadf6070790dd3750e4f515b8aa9c8b Mon Sep 17 00:00:00 2001 From: tnoho Date: Sun, 27 Aug 2023 16:58:45 +0900 Subject: [PATCH 27/33] =?UTF-8?q?SoraAudioStreamSink=20=E3=81=AB=E6=94=B9?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- ...o_sink2.cpp => sora_audio_stream_sink.cpp} | 18 +++++++------- ...audio_sink2.h => sora_audio_stream_sink.h} | 24 +++++++++---------- src/sora_sdk/__init__.py | 2 +- src/sora_sdk_ext.cpp | 13 +++++----- 5 files changed, 30 insertions(+), 29 deletions(-) rename src/{sora_audio_sink2.cpp => sora_audio_stream_sink.cpp} (92%) rename src/{sora_audio_sink2.h => sora_audio_stream_sink.h} (89%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 35bebf97..695b4411 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,7 @@ nanobind_add_module( src/dummy_audio_mixer.cpp src/sora.cpp src/sora_audio_sink.cpp - src/sora_audio_sink2.cpp + src/sora_audio_stream_sink.cpp src/sora_audio_source.cpp src/sora_connection.cpp src/sora_factory.cpp diff --git a/src/sora_audio_sink2.cpp b/src/sora_audio_stream_sink.cpp similarity index 92% rename from src/sora_audio_sink2.cpp rename to src/sora_audio_stream_sink.cpp index 4eca2d6a..11193df2 100644 --- a/src/sora_audio_sink2.cpp +++ b/src/sora_audio_stream_sink.cpp @@ -1,4 +1,4 @@ -#include "sora_audio_sink2.h" +#include "sora_audio_stream_sink.h" #include @@ -135,9 +135,9 @@ std::optional SoraAudioFrame::absolute_capture_timestamp_ms() const { return impl_->absolute_capture_timestamp_ms(); } -SoraAudioSink2Impl::SoraAudioSink2Impl(SoraTrackInterface* track, - int output_sample_rate, - size_t output_channels) +SoraAudioStreamSinkImpl::SoraAudioStreamSinkImpl(SoraTrackInterface* track, + int output_sample_rate, + size_t output_channels) : track_(track), output_sample_rate_(output_sample_rate), output_channels_(output_channels) { @@ -147,18 +147,18 @@ SoraAudioSink2Impl::SoraAudioSink2Impl(SoraTrackInterface* track, audio_track->AddSink(this); } -SoraAudioSink2Impl::~SoraAudioSink2Impl() { +SoraAudioStreamSinkImpl::~SoraAudioStreamSinkImpl() { Del(); } -void SoraAudioSink2Impl::Del() { +void SoraAudioStreamSinkImpl::Del() { if (track_) { track_->RemoveSubscriber(this); } Disposed(); } -void SoraAudioSink2Impl::Disposed() { +void SoraAudioStreamSinkImpl::Disposed() { if (track_ && track_->GetTrack()) { webrtc::AudioTrackInterface* audio_track = static_cast(track_->GetTrack().get()); @@ -167,11 +167,11 @@ void SoraAudioSink2Impl::Disposed() { track_ = nullptr; } -void SoraAudioSink2Impl::PublisherDisposed() { +void SoraAudioStreamSinkImpl::PublisherDisposed() { Disposed(); } -void SoraAudioSink2Impl::OnData( +void SoraAudioStreamSinkImpl::OnData( const void* audio_data, int bits_per_sample, int sample_rate, diff --git a/src/sora_audio_sink2.h b/src/sora_audio_stream_sink.h similarity index 89% rename from src/sora_audio_sink2.h rename to src/sora_audio_stream_sink.h index d72d1c5d..fa9b2152 100644 --- a/src/sora_audio_sink2.h +++ b/src/sora_audio_stream_sink.h @@ -30,7 +30,7 @@ class SoraAudioFrameImpl { }; /** - * SoraAudioFrame を SoraAudioSink2Impl から生成した際にデータを持つクラスです。 + * SoraAudioFrame を SoraAudioStreamSinkImpl から生成した際にデータを持つクラスです。 * * libwebrtc でオーディオデータを扱う際の単位である webrtc::AudioFrame のまま扱います。 */ @@ -81,7 +81,7 @@ class SoraAudioFrameVectorImpl : public SoraAudioFrameImpl { /** * 受信した 10ms 単位の音声データを保持する SoraAudioFrame です。 * - * SoraAudioSink2Impl から生成するための webrtc::AudioFrame を引数にもつコンストラクタと + * SoraAudioStreamSinkImpl から生成するための webrtc::AudioFrame を引数にもつコンストラクタと * pickle に対応するための Python から生成ためのコンストラクタが存在します。 * それぞれでデータの持ち方が異なるため実際のデータは SoraAudioFrameImpl の impl_ 内にもっていて、 * このクラスは Python へのインターフェイスを提供します。 @@ -89,7 +89,7 @@ class SoraAudioFrameVectorImpl : public SoraAudioFrameImpl { */ class SoraAudioFrame { public: - // SoraAudioSink2Impl から生成する際のコンストラクタ + // SoraAudioStreamSinkImpl から生成する際のコンストラクタ SoraAudioFrame(std::unique_ptr audio_frame); // pickle した状態から __setstate__ で戻す際に使うコンストラクタ SoraAudioFrame(std::vector vector, @@ -149,7 +149,7 @@ class SoraAudioFrame { }; /** - * Sora からの音声を受け取る SoraAudioSink2Impl です。 + * Sora からの音声を受け取る SoraAudioStreamSinkImpl です。 * * Connection の OnTrack コールバックから渡されるリモート Track から音声を取り出すことができます。 * Track からの音声はコンストラクタで設定したサンプリングレートとチャネル数に変換し、 @@ -158,16 +158,16 @@ class SoraAudioFrame { * コールバックは速やかに処理を返すことが求められます。 10ms 単位の高いリアルタム性を求めないのであれば、 * 内部にバッファを持ち任意のタイミングで音声を取り出すことができる SoraAudioSink の利用を推奨します。 * - * 実装上の留意点:Track の参照保持のための Impl のない SoraAudioSink2 を __init__.py に定義しています。 - * SoraAudioSink2Impl を直接 Python から呼び出すことは想定していません。 + * 実装上の留意点:Track の参照保持のための Impl のない SoraAudioStreamSink を __init__.py に定義しています。 + * SoraAudioStreamSinkImpl を直接 Python から呼び出すことは想定していません。 */ -class SoraAudioSink2Impl : public webrtc::AudioTrackSinkInterface, - public DisposeSubscriber { +class SoraAudioStreamSinkImpl : public webrtc::AudioTrackSinkInterface, + public DisposeSubscriber { public: - SoraAudioSink2Impl(SoraTrackInterface* track, - int output_sample_rate, - size_t output_channels); - ~SoraAudioSink2Impl(); + SoraAudioStreamSinkImpl(SoraTrackInterface* track, + int output_sample_rate, + size_t output_channels); + ~SoraAudioStreamSinkImpl(); void Del(); void Disposed(); diff --git a/src/sora_sdk/__init__.py b/src/sora_sdk/__init__.py index 33c42967..227fb0c6 100644 --- a/src/sora_sdk/__init__.py +++ b/src/sora_sdk/__init__.py @@ -18,7 +18,7 @@ def __del__(self): del self.__track -class SoraAudioSink2(SoraAudioSink2Impl): +class SoraAudioStreamSink(SoraAudioStreamSinkImpl): def __init__(self, track, output_frequency, output_channels): super().__init__(track, output_frequency, output_channels) self.__track = track diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index 0139463e..4164d311 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -10,8 +10,8 @@ #include "sora.h" #include "sora_audio_sink.h" -#include "sora_audio_sink2.h" #include "sora_audio_source.h" +#include "sora_audio_stream_sink.h" #include "sora_connection.h" #include "sora_log.h" #include "sora_track_interface.h" @@ -48,7 +48,8 @@ PyType_Slot audio_sink_slots[] = { {0, nullptr}}; int audio_sink2_tp_traverse(PyObject* self, visitproc visit, void* arg) { - SoraAudioSink2Impl* audio_sink = nb::inst_ptr(self); + SoraAudioStreamSinkImpl* audio_sink = + nb::inst_ptr(self); if (audio_sink->on_frame_) { nb::object on_frame = nb::cast(audio_sink->on_frame_, nb::rv_policy::none); @@ -230,12 +231,12 @@ NB_MODULE(sora_sdk_ext, m) { &SoraAudioFrame::absolute_capture_timestamp_ms) .def("data", &SoraAudioFrame::Data, nb::rv_policy::reference); - nb::class_(m, "SoraAudioSink2Impl", - nb::type_slots(audio_sink2_slots)) + nb::class_(m, "SoraAudioStreamSinkImpl", + nb::type_slots(audio_sink2_slots)) .def(nb::init(), "track"_a, "output_frequency"_a = -1, "output_channels"_a = 0) - .def("__del__", &SoraAudioSink2Impl::Del) - .def_rw("on_frame", &SoraAudioSink2Impl::on_frame_); + .def("__del__", &SoraAudioStreamSinkImpl::Del) + .def_rw("on_frame", &SoraAudioStreamSinkImpl::on_frame_); nb::class_(m, "SoraVideoFrame") .def("data", &SoraVideoFrame::Data, nb::rv_policy::reference); From 2b0f369e6fb7ca74ee110fbee5962e59abd17425 Mon Sep 17 00:00:00 2001 From: voluntas Date: Mon, 28 Aug 2023 10:23:47 +0900 Subject: [PATCH 28/33] =?UTF-8?q?sink2=20=E3=82=92=20stream=5Fsink=20?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_stream_sink.h | 4 ++-- src/sora_sdk_ext.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sora_audio_stream_sink.h b/src/sora_audio_stream_sink.h index fa9b2152..45795eba 100644 --- a/src/sora_audio_stream_sink.h +++ b/src/sora_audio_stream_sink.h @@ -1,5 +1,5 @@ -#ifndef SORA_AUDIO_SINK2_H_ -#define SORA_AUDIO_SINK2_H_ +#ifndef SORA_AUDIO_STREAM_SINK_H_ +#define SORA_AUDIO_STREAM_SINK_H_ // nonobind #include diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index f12e6d57..767a0487 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -56,7 +56,7 @@ PyType_Slot audio_sink_slots[] = { {Py_tp_traverse, (void*)audio_sink_tp_traverse}, {0, nullptr}}; -int audio_sink2_tp_traverse(PyObject* self, visitproc visit, void* arg) { +int audio_stream_sink_tp_traverse(PyObject* self, visitproc visit, void* arg) { SoraAudioStreamSinkImpl* audio_sink = nb::inst_ptr(self); @@ -68,8 +68,8 @@ int audio_sink2_tp_traverse(PyObject* self, visitproc visit, void* arg) { return 0; } -PyType_Slot audio_sink2_slots[] = { - {Py_tp_traverse, (void*)audio_sink2_tp_traverse}, +PyType_Slot audio_stream_sink_slots[] = { + {Py_tp_traverse, (void*)audio_stream_sink_tp_traverse}, {0, nullptr}}; int video_sink_tp_traverse(PyObject* self, visitproc visit, void* arg) { @@ -244,7 +244,7 @@ NB_MODULE(sora_sdk_ext, m) { .def("data", &SoraAudioFrame::Data, nb::rv_policy::reference); nb::class_(m, "SoraAudioStreamSinkImpl", - nb::type_slots(audio_sink2_slots)) + nb::type_slots(audio_stream_sink_slots)) .def(nb::init(), "track"_a, "output_frequency"_a = -1, "output_channels"_a = 0) .def("__del__", &SoraAudioStreamSinkImpl::Del) From 82ca329659df27fff574d4c68b7aac5c9ad3d722 Mon Sep 17 00:00:00 2001 From: voluntas Date: Mon, 28 Aug 2023 10:32:11 +0900 Subject: [PATCH 29/33] =?UTF-8?q?=E5=A4=89=E6=9B=B4=E5=B1=A5=E6=AD=B4?= =?UTF-8?q?=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 333b3a32..0d349d14 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,10 @@ ## develop +- [ADD] リアルタイム性を重視した AudioStreamSink の追加 + - @tnoho +- [ADD] AudioStreamSink が返す音声フレームとして pickel が可能な AudioFrame を追加 + - @tnoho - [UPDATE] Sora C++ SDK のバージョンを 2023.10.0 に上げる - @voluntas From b05f26e2db72b5ed222bb7506bbf182e7d85d246 Mon Sep 17 00:00:00 2001 From: tnoho Date: Mon, 28 Aug 2023 18:56:12 +0900 Subject: [PATCH 30/33] =?UTF-8?q?Windows=20=E3=81=A7=E3=83=93=E3=83=AB?= =?UTF-8?q?=E3=83=89=E3=81=8C=E9=80=9A=E3=82=89=E3=81=AA=E3=81=84=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_audio_stream_sink.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sora_audio_stream_sink.h b/src/sora_audio_stream_sink.h index 45795eba..affd0b88 100644 --- a/src/sora_audio_stream_sink.h +++ b/src/sora_audio_stream_sink.h @@ -1,6 +1,8 @@ #ifndef SORA_AUDIO_STREAM_SINK_H_ #define SORA_AUDIO_STREAM_SINK_H_ +#include + // nonobind #include #include From 6481052d943810b2796490289d338f7d8a05b29b Mon Sep 17 00:00:00 2001 From: tnoho Date: Mon, 28 Aug 2023 20:16:29 +0900 Subject: [PATCH 31/33] =?UTF-8?q?Revert=20"PR=20=E3=82=92=E5=88=86?= =?UTF-8?q?=E3=81=91=E3=82=8B=E3=81=9F=E3=82=81=E3=80=81=20VAD=20=E3=82=92?= =?UTF-8?q?=E4=B8=80=E6=97=A6=E5=89=8A=E9=99=A4=E3=81=99=E3=82=8B"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a3d0c0857fa22688c8207cf7af6e38e898ed3e7e. --- CMakeLists.txt | 1 + src/sora_sdk_ext.cpp | 5 +++++ src/sora_vad.cpp | 42 ++++++++++++++++++++++++++++++++++++++++ src/sora_vad.h | 46 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 src/sora_vad.cpp create mode 100644 src/sora_vad.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 695b4411..87b59462 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ nanobind_add_module( src/sora_factory.cpp src/sora_log.cpp src/sora_sdk_ext.cpp + src/sora_vad.cpp src/sora_video_sink.cpp src/sora_video_source.cpp ) diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index 767a0487..1c1709a6 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -15,6 +15,7 @@ #include "sora_connection.h" #include "sora_log.h" #include "sora_track_interface.h" +#include "sora_vad.h" #include "sora_video_sink.h" #include "sora_video_source.h" @@ -250,6 +251,10 @@ NB_MODULE(sora_sdk_ext, m) { .def("__del__", &SoraAudioStreamSinkImpl::Del) .def_rw("on_frame", &SoraAudioStreamSinkImpl::on_frame_); + nb::class_(m, "SoraVAD") + .def(nb::init<>()) + .def("analyze", &SoraVAD::Analyze); + nb::class_(m, "SoraVideoFrame") .def("data", &SoraVideoFrame::Data, nb::rv_policy::reference); diff --git a/src/sora_vad.cpp b/src/sora_vad.cpp new file mode 100644 index 00000000..44df8a2d --- /dev/null +++ b/src/sora_vad.cpp @@ -0,0 +1,42 @@ +#include "sora_vad.h" + +#include + +// WebRTC +#include +#include +#include +#include +#include +#include + +SoraVAD::SoraVAD() { + vad_ = std::make_unique( + webrtc::kVadResetPeriodMs, // libWebRTC 内部の設定に合わせる + webrtc::GetAvailableCpuFeatures(), + webrtc::rnn_vad:: + kSampleRate24kHz // 24kHz にしておかないと、VAD 前にリサンプリングが走る + ); +} + +float SoraVAD::Analyze(std::shared_ptr frame) { + if (!audio_buffer_ || + vad_input_config_.sample_rate_hz() != frame->sample_rate_hz() || + vad_input_config_.num_channels() != frame->num_channels()) { + // audio_buffer_ のサンプリングレートやチャネル数と frame のそれが一致しない場合は audio_buffer_ を初期化する + audio_buffer_.reset(new webrtc::AudioBuffer( + frame->sample_rate_hz(), frame->num_channels(), + webrtc::rnn_vad::kSampleRate24kHz, // VAD は 24kHz なので合わせる + 1, // VAD は 1 チャンネルなので合わせる + webrtc::rnn_vad:: + kSampleRate24kHz, // 出力はしないが、余計なインスタンスを生成しないよう合わせる + 1 // 出力はしないが VAD とチャネル数は合わせておく + )); + vad_input_config_ = + webrtc::StreamConfig(frame->sample_rate_hz(), frame->num_channels()); + } + audio_buffer_->CopyFrom(frame->RawData(), vad_input_config_); + return vad_->Analyze(webrtc::AudioFrameView( + audio_buffer_->channels(), audio_buffer_->num_channels(), + audio_buffer_->num_frames())); +} diff --git a/src/sora_vad.h b/src/sora_vad.h new file mode 100644 index 00000000..fd4d7bc1 --- /dev/null +++ b/src/sora_vad.h @@ -0,0 +1,46 @@ +#ifndef SORA_VAD_H_ +#define SORA_VAD_H_ + +// nonobind +#include +#include + +// WebRTC +#include +#include +#include +#include + +#include "sora_audio_sink2.h" + +namespace nb = nanobind; + +/** + * SoraAudioFrame の音声データに音声である率を返す VAD です。 + * + * 受信した音声データに対して何らかの処理を Python で行う場合、 + * 全体の負荷軽減を考えるのであれば音声データから音声であると推測されるデータのみに、 + * 処理を行うようにした方が全体の負荷を下げることができます。 + * libwebrtc には優秀な VAD が含まれているため、これを活用したユーティリティクラスとして用意しました。 + */ +class SoraVAD { + public: + SoraVAD(); + + /** + * SoraAudioFrame 内の音声データが音声である確率を返します。 + * + * libwebrtc 内部では 0.95 より大きい場合に音声とみなしています。 + * + * @param frame 音声である確率を求める SoraAudioFrame + * @return 0 - 1 で表される音声である確率 + */ + float Analyze(std::shared_ptr frame); + + private: + std::unique_ptr audio_buffer_; + webrtc::StreamConfig vad_input_config_; + std::unique_ptr vad_; +}; + +#endif \ No newline at end of file From 8da8a2e9a2b78c5455f265ea1c2809ff8068370d Mon Sep 17 00:00:00 2001 From: tnoho Date: Mon, 28 Aug 2023 20:20:08 +0900 Subject: [PATCH 32/33] =?UTF-8?q?include=20=E3=82=92=20sora=5Faudio=5Fstre?= =?UTF-8?q?am=5Fsink=20=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sora_vad.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sora_vad.h b/src/sora_vad.h index fd4d7bc1..24106891 100644 --- a/src/sora_vad.h +++ b/src/sora_vad.h @@ -11,7 +11,7 @@ #include #include -#include "sora_audio_sink2.h" +#include "sora_audio_stream_sink.h" namespace nb = nanobind; From 9043c3591178c3966d7d74b99c655c9e7f703a21 Mon Sep 17 00:00:00 2001 From: tnoho Date: Mon, 28 Aug 2023 20:21:12 +0900 Subject: [PATCH 33/33] =?UTF-8?q?CHANGES=20=E3=82=92=E8=A8=98=E8=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 0d349d14..7b69ae43 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,8 @@ ## develop +- [ADD] 発話区間の検出が可能な SoraVAD の追加 + - @tnoho - [ADD] リアルタイム性を重視した AudioStreamSink の追加 - @tnoho - [ADD] AudioStreamSink が返す音声フレームとして pickel が可能な AudioFrame を追加