Upgrade to Pro — share decks privately, control downloads, hide ads and more …

get updated to the latest realtime audio processings knowledge base (2023) (再履修: 2023年までの リアルタイムオーディオ処理)

Atsushi Eno
February 28, 2023

get updated to the latest realtime audio processings knowledge base (2023) (再履修: 2023年までの リアルタイムオーディオ処理)

Atsushi Eno

February 28, 2023
Tweet

More Decks by Atsushi Eno

Other Decks in Technology

Transcript

  1. 再履修: 2023年までの
    リアルタイムオーディオ処理
    atsushieno

    View full-size slide

  2. このLTの趣旨
    これまでにオーディオ開発者向けに公開されたセッション動画や資料をもとに、リアルタ
    イムオーディオ処理にまつわる(主にC++の)知見をまとめていきます
    いろんなテクニカルタームを出していきます
    特にオチはないので時間のある限り話していきます

    View full-size slide

  3. 参考資料(テキスト、動画)
    Real-Time audio processing 101: time waits for nothing (Ross Bencina) 🔗
    ADC19 - Real-Time 101 Pt. I / Pt. II
    ADC21 - C++ Standard Library for Real-time Audio 🔗
    "Allocators: the Good Parts" 🔗
    "practical memory pool based allocators for modern C++"
    ADC22 - Thread synchronisation in real-time audio processing with RCU (Read-Copy-Update)
    The Ardour DAW – Latency Compensation and Anywhere-to-Anywhere Signal Routing Systems
    ...

    View full-size slide

  4. リアルタイムオーディオ処理
    一定時間に必ず処理を終えないと音飛び・ノイズの原因になる
    処理速度は重要だが、「一定時間に必ず処理を終える」ことが最優先
    ※WCET (worse case execution time) が数msec.以下になること
    オーディオ処理はリアルタイムスレッドで行う
    オーディオ処理以外との協調動作は適切に実装する必要がある(GUI, I/Oなど)

    View full-size slide

  5. リアルタイム処理の一般原則
    システムコールは基本的にNG
    - ロックNG
    - メモリ確保NG
    計算量 O(1) より大きいものNG
    例外はNG
    C++コルーチンはNG

    View full-size slide

  6. メモリ確保
    動的メモリ確保 / 解放を避ける(システムコール)
    - 解放も(開放時にシステムが複雑なコンパクションを行う可能性)
    std::shared_ptrをatomicに操作することはできない
    std::vectorのコピー操作なども動的メモリ確保/解放がありうる
    コピーコンストラクタやコピー代入演算子も潰しておくのが安全(` = deleted`)

    View full-size slide

  7. メモリ確保
    静的メモリ確保で何とかする
    - リングバッファ
    - choc::SmallVector: 確保済みメモリでstd::vectorライクなAPI
    - choc::sorting::stable_sort : 動的メモリ確保しないstd::stable_sortライクなAPI
    - static_vector?

    View full-size slide

  8. 動的メモリ確保はもはや禁忌ではない?
    std::pmr (polymorphic memory resource) でリアルタイム動的メモリ確保
    - libc++で未実装 / cradleapps/realtime_memory (プライベート実装)
    - 事前アロケーション、 single threaded, O(1)
    - std::pmr::monotonic_buffer_resource,
    std::pmr::unsynchronised_pool_resrouce, std::pmr_polymorphic_allocator
    - copy constructorやmoveなどでallocatorはコピーされない(!)
    - std::mapではなくstd::pmr::mapが用意されている
    ※tcmalloc、rpmalocなどモダンな汎用アロケーターもnon RT-safe
    ※原理原則は変わっていない
     (これらはシステムレベルの確保/解放を行わない)

    View full-size slide

  9. オーディオスレッドと排他制御
    ADC19 Real Time 101のフローチャート(25分目くらい)
    「複数のスレッドではどうデータ共有するのが適切か?」→ 条件による

    View full-size slide

  10. オーディオスレッドと排他制御
    全部説明はできないので簡潔に
    - データ共有はない? → FIFO
    - RTスレッドはない? → std::mutex
    - 小さいデータ? → std::atomic
    - リソース確保失敗してもよい? → try_lock
    - RTスレッドがデータを更新しない? → CAS
    - non-RTスレッドがデータを更新しない? → ダブルバッファリング
    - どっちも更新しうる → CAS + FIFO でがんばる…
    ※CAS: (atomic) compare and set (or swap)

    View full-size slide

  11. オーディオスレッドと排他制御
    補足:
    「小さいデータ」判定: std::atomic::is_always_lock_free
    volatileはメモリモデルの規定がないので
    std::atomicを使うべき
    「リソース確保の失敗してもよい」場合 - ウェーブテーブルの更新など(空白で再生)
    std::try_to_lockはOKだがstd::mutexはリリース時にブロックの可能性があるので
    NG
    (多分そんな実装は多くないけど標準としては
    wait-freeではない)
    std::mutexの代わりにstd::atomic_flagを使った単純なspin lockを使えばOK
    CAS loop : non-atomicな構造体をオーディオループ外から更新する場合など
    ABA問題など各種の罠を回避できる
    farbot::NonRealtimeMutableが便利

    View full-size slide

  12. オーディオスレッドと排他制御
    double buffering (RTスレッドが更新するスペクトラムを
    non-RTスレッドが読む場合 etc.)
    farbot::RealtimeMutableが便利
    RTスレッドもnon-RTスレッドもデータを操作しうる場合
    リングバッファを活用したfarbot FIFOが有用。ただオプションが多い
    :
    - single consumerかmulti consumerか?
    - single producerかmulti producerか?
    - ロック確保失敗時nullを返してもいいか?
    - etc...
    -
    複数スレッドが変更するのはほぼ無理なので
    変更は1つのスレッドに任せるべき

    View full-size slide

  13. RCU (read-copy-update)
    RTスレッドとnon-RTスレッドでのデータ共有はLinuxなどでRCUとして一般的
    Deferred reclamation (遅延delete)
    crill-dev/crill on GitHub - seqlock_object : load(), try_load() and store()
    - readerとリソースにgenerationを付けるみたいな実装

    View full-size slide

  14. 非同期呼び出し
    タスクを投げっぱなしにするのは賢いやり方
    RT safeに投げる仕組み(non-allocating, non-blocking)を使う
    JUCE::callAsync(), farbot::AsyncCallerなど
    lambdaそのものを渡すのはOK
    lambdaを使うときにはアロケーションが発生しうる

    View full-size slide

  15. >O(1): 計算時間が未知の処理
    (ならし計算量, 償却計算量)でのO(1)もNG
    STL: std::arrayだけはRT-safe、他はallocatorなどに依存
    - 要素型とiteratorの型がRT-safeであることは必須

    View full-size slide

  16. 例外
    throw - not RT-safe
    tryブロックに入って例外を投げないうちはRT-safe? -> YESっぽい
    - x86以外はRT-safe / x86: 実行時にunwindを動的に生成
    関連: std::variantはRT-safeだがboost::variantはnot RT-safe
    - 例外発生時に例外オブジェクトを一時退避確保する挙動
    MSのZero-overhead deterministic exceptionsのC++ draft

    View full-size slide

  17. その他
    CPUキャッシュを活用できるようにコンテキストスイッチをなるべく回避する
    型消去 : non RT-safe (std::any, std::function, ...)
    コルーチン: フレームメモリ確保が発生
    rand() : 実装が「生成した数が重複しないようにロックする」可能性がある

    View full-size slide