Apply global audio options at the engine level

Introduce ApplyGlobalOptions to the MediaEngine interface to configure
global audio processing options (such as AEC, AGC, and noise
suppression) directly at the engine level. This replaces the previous
behavior where media channels implicitly re-applied options to the
engine during initialization and parameter updates.

Applying global settings during CreateAudioSource ensures engine-level
configurations persist reliably across stream lifecycles. This change
also separates global audio processing options from stream-level
parameters like jitter buffer and audio network adaptor settings.

                 [Before Decoupling]
+----------------------------------------------------+
|      AudioOptions (AEC/AGC/NS + Jitter/ANA)        |
+----------------------------------------------------+
  /                                                \
 / (engine()->ApplyOptions)                         \
v                                                    v
WebRtcVoiceEngine (Global APM)             VoiceSend/RecvChannel


              [After Decoupling (This CL)]
+-----------------------+                  +-----------------------+
|  Global Options (APM) |                  |  Channel Options (ST) |
|  - echo_cancellation  |                  |  - jitter_buffer      |
|  - auto_gain_control  |                  |  - audio_network_     |
|  - noise_suppression  |                  |    adaptor            |
+-----------------------+                  +-----------------------+
           |                                           |
           v (ApplyGlobalOptions)                      v
   WebRtcVoiceEngine (APM)                     VoiceSend/RecvChannel
   - Stores in global_options_                 - No ApplyOptions calls
   - Persisted across cycles                   - No leakage / racing

Bug: webrtc:42224170
Change-Id: I3c23293ae338495ee32330ce5287411947f6d472
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/476340
Reviewed-by: Per Ã…hgren <peah@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47844}
diff --git a/api/peer_connection_interface.h b/api/peer_connection_interface.h
index da0f292..e1ba0a6 100644
--- a/api/peer_connection_interface.h
+++ b/api/peer_connection_interface.h
@@ -1552,7 +1552,10 @@
       const std::string& stream_id) = 0;
 
   // Creates an AudioSourceInterface.
-  // `options` decides audio processing settings.
+  // The `options` specified here are elevated and applied globally at the media
+  // engine level to configure global audio processing settings (like APM
+  // options for AEC, AGC, and NS). These options persist reliably across stream
+  // lifecycles.
   virtual scoped_refptr<AudioSourceInterface> CreateAudioSource(
       const AudioOptions& options) = 0;
 
diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h
index d677c38..6d35b3e 100644
--- a/media/base/fake_media_engine.h
+++ b/media/base/fake_media_engine.h
@@ -854,6 +854,7 @@
   bool StartAecDump(FileWrapper file, int64_t max_size_bytes) override;
   void StopAecDump() override;
   std::optional<AudioDeviceModule::Stats> GetAudioDeviceStats() override;
+  void ApplyGlobalOptions(const AudioOptions& options) override {}
   std::vector<RtpHeaderExtensionCapability> GetRtpHeaderExtensions(
       const FieldTrialsView* field_trials) const override;
   void SetRtpHeaderExtensions(
diff --git a/media/base/media_engine.h b/media/base/media_engine.h
index 67850ae..38726cf 100644
--- a/media/base/media_engine.h
+++ b/media/base/media_engine.h
@@ -94,6 +94,9 @@
   virtual ~VoiceChannelFactoryInterface() = default;
 
   // Safe to be called from the signaling thread.
+  // The `options` parameter configures stream/channel-specific settings (e.g.,
+  // jitter buffer, ANA). Global options (like AEC, AGC, NS) should be
+  // configured directly at the engine level via ApplyGlobalOptions.
   virtual std::unique_ptr<VoiceMediaSendChannelInterface> CreateSendChannel(
       const Environment& env,
       Call* call,
@@ -103,6 +106,9 @@
       absl::AnyInvocable<void()> parameters_changed_callback = nullptr) = 0;
 
   // Safe to be called from the signaling thread.
+  // The `options` parameter configures stream/channel-specific settings (e.g.,
+  // jitter buffer). Global options (like AEC, AGC, NS) should be configured
+  // directly at the engine level via ApplyGlobalOptions.
   virtual std::unique_ptr<VoiceMediaReceiveChannelInterface>
   CreateReceiveChannel(const Environment& env,
                        Call* call,
@@ -155,6 +161,8 @@
   virtual void Init() = 0;
   // Stops the engine.
   virtual void Terminate() = 0;
+  // Applies global options (like APM settings) to the engine.
+  virtual void ApplyGlobalOptions(const AudioOptions& options) = 0;
 
   // TODO(solenberg): Remove once VoE API refactoring is done.
   virtual scoped_refptr<AudioState> GetAudioState() const = 0;
diff --git a/media/engine/fake_webrtc_call.h b/media/engine/fake_webrtc_call.h
index f1f47b1..2292d91 100644
--- a/media/engine/fake_webrtc_call.h
+++ b/media/engine/fake_webrtc_call.h
@@ -155,8 +155,12 @@
       bool get_and_clear_legacy_stats) const override;
   void SetSink(AudioSinkInterface* sink) override;
   void SetGain(float gain) override;
-  void SetJitterBufferMaxPackets(size_t max_packets) override {}
-  void SetJitterBufferFastAccelerate(bool fast_accelerate) override {}
+  void SetJitterBufferMaxPackets(size_t max_packets) override {
+    config_.jitter_buffer_max_packets = max_packets;
+  }
+  void SetJitterBufferFastAccelerate(bool fast_accelerate) override {
+    config_.jitter_buffer_fast_accelerate = fast_accelerate;
+  }
   bool SetBaseMinimumPlayoutDelayMs(int delay_ms) override {
     base_mininum_playout_delay_ms_ = delay_ms;
     return true;
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index 9954e24..920d7cc 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -129,6 +129,21 @@
 const int kMinPayloadType = 0;
 const int kMaxPayloadType = 127;
 
+AudioOptions CreateDefaultAudioOptions() {
+  AudioOptions options;
+  options.echo_cancellation = true;
+  options.auto_gain_control = true;
+#if defined(WEBRTC_IOS)
+  // On iOS, VPIO provides built-in NS.
+  options.noise_suppression = false;
+#else
+  options.noise_suppression = true;
+#endif
+  options.highpass_filter = true;
+  options.stereo_swapping = false;
+  return options;
+}
+
 class ProxySink : public AudioSinkInterface {
  public:
   explicit ProxySink(AudioSinkInterface* sink) : sink_(sink) {
@@ -530,23 +545,9 @@
   adm_helpers::Init(adm());
 
   // Set default engine options.
-  {
-    AudioOptions options;
-    options.echo_cancellation = true;
-    options.auto_gain_control = true;
-#if defined(WEBRTC_IOS)
-    // On iOS, VPIO provides built-in NS.
-    options.noise_suppression = false;
-#else
-    options.noise_suppression = true;
-#endif
-    options.highpass_filter = true;
-    options.stereo_swapping = false;
-    options.audio_jitter_buffer_max_packets = 200;
-    options.audio_jitter_buffer_fast_accelerate = false;
-    options.audio_jitter_buffer_min_delay_ms = 0;
-    ApplyOptions(options);
-  }
+  AudioOptions options = CreateDefaultAudioOptions();
+  options.SetAll(global_options_);
+  ApplyOptions(options);
 
   // Connect the ADM to our audio path. It's important to do this after applying
   // the configuration so that the audio callback receives calls with the
@@ -603,6 +604,12 @@
                                                      crypto_options, call);
 }
 
+void WebRtcVoiceEngine::ApplyGlobalOptions(const AudioOptions& options) {
+  RTC_DCHECK_RUN_ON(&worker_thread_checker_);
+  global_options_.SetAll(options);
+  ApplyOptions(options);
+}
+
 void WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) {
   RTC_DCHECK_RUN_ON(&worker_thread_checker_);
   RTC_LOG(LS_INFO) << "WebRtcVoiceEngine::ApplyOptions: "
@@ -1299,7 +1306,6 @@
   // on top.  This means there is no way to "clear" options such that
   // they go back to the engine default.
   options_.SetAll(options);
-  engine()->ApplyOptions(options_);
 
   std::optional<std::string> audio_network_adaptor_config =
       GetAudioNetworkAdaptorConfig(options_);
@@ -1563,8 +1569,6 @@
 
   // Apply channel specific options.
   if (send) {
-    engine()->ApplyOptions(options_);
-
     // Initialize the ADM for recording (this may take time on some platforms,
     // e.g. Android).
     if (options_.init_recording_on_send.value_or(true) &&
@@ -2260,7 +2264,6 @@
   // on top.  This means there is no way to "clear" options such that
   // they go back to the engine default.
   options_.SetAll(options);
-  engine()->ApplyOptions(options_);
 
   // Check if any options changed that should apply to receive streams.
   if (options.audio_jitter_buffer_max_packets &&
diff --git a/media/engine/webrtc_voice_engine.h b/media/engine/webrtc_voice_engine.h
index e45456b..879a102 100644
--- a/media/engine/webrtc_voice_engine.h
+++ b/media/engine/webrtc_voice_engine.h
@@ -120,10 +120,14 @@
     return decoder_factory_;
   }
 
-  // Every option that is "set" will be applied. Every option not "set" will be
-  // ignored. This allows us to selectively turn on and off different options
-  // easily at any time.
+  // Applies channel/stream level options. Every option that is "set" will be
+  // applied, and others will be ignored.
   void ApplyOptions(const AudioOptions& options);
+  // Applies global engine-level processing options (e.g. APM settings like AEC,
+  // AGC, NS). Global options govern all processing; local channel-level
+  // settings for these fields are ignored and do not override the global
+  // configuration.
+  void ApplyGlobalOptions(const AudioOptions& options) override;
 
   AudioDeviceModule* adm();
   AudioProcessing* apm() const;
@@ -167,6 +171,7 @@
   const std::vector<Codec> legacy_send_codecs_;
   const std::vector<Codec> legacy_recv_codecs_;
   bool initialized_ RTC_GUARDED_BY(worker_thread_checker_) = false;
+  AudioOptions global_options_ RTC_GUARDED_BY(worker_thread_checker_);
 };
 
 class WebRtcVoiceSendChannel final : public MediaChannelUtil,
diff --git a/media/engine/webrtc_voice_engine_unittest.cc b/media/engine/webrtc_voice_engine_unittest.cc
index b206d05..3b71350 100644
--- a/media/engine/webrtc_voice_engine_unittest.cc
+++ b/media/engine/webrtc_voice_engine_unittest.cc
@@ -3118,97 +3118,6 @@
   TestExtmapAllowMixedCallee(/*extmap_allow_mixed=*/false);
 }
 
-TEST_P(WebRtcVoiceEngineTestFake, SetAudioOptions) {
-  EXPECT_TRUE(SetupSendStream());
-  EXPECT_TRUE(AddRecvStream(kSsrcY));
-  EXPECT_CALL(*adm_, BuiltInAECIsAvailable())
-      .Times(8)
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(*adm_, BuiltInAGCIsAvailable())
-      .Times(4)
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(*adm_, BuiltInNSIsAvailable())
-      .Times(2)
-      .WillRepeatedly(Return(false));
-
-  EXPECT_EQ(200u, GetRecvStreamConfig(kSsrcY).jitter_buffer_max_packets);
-  EXPECT_FALSE(GetRecvStreamConfig(kSsrcY).jitter_buffer_fast_accelerate);
-
-  // Nothing set in AudioOptions, so everything should be as default.
-  send_parameters_.options = AudioOptions();
-  SetSenderParameters(send_parameters_);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    EXPECT_TRUE(IsHighPassFilterEnabled());
-  }
-  EXPECT_EQ(200u, GetRecvStreamConfig(kSsrcY).jitter_buffer_max_packets);
-  EXPECT_FALSE(GetRecvStreamConfig(kSsrcY).jitter_buffer_fast_accelerate);
-
-  // Turn echo cancellation off
-  send_parameters_.options.echo_cancellation = false;
-  SetSenderParameters(send_parameters_);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/false);
-  }
-
-  // Turn echo cancellation back on, with settings, and make sure
-  // nothing else changed.
-  send_parameters_.options.echo_cancellation = true;
-  SetSenderParameters(send_parameters_);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-  }
-
-  // Turn off echo cancellation and delay agnostic aec.
-  send_parameters_.options.echo_cancellation = false;
-  SetSenderParameters(send_parameters_);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/false);
-  }
-
-  // Restore AEC to be on to work with the following tests.
-  send_parameters_.options.echo_cancellation = true;
-  SetSenderParameters(send_parameters_);
-
-  // Turn off AGC
-  send_parameters_.options.auto_gain_control = false;
-  SetSenderParameters(send_parameters_);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    EXPECT_FALSE(apm_config_.gain_controller1.enabled);
-  }
-
-  // Turn AGC back on
-  send_parameters_.options.auto_gain_control = true;
-  SetSenderParameters(send_parameters_);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    EXPECT_TRUE(apm_config_.gain_controller1.enabled);
-  }
-
-  // Turn off other options.
-  send_parameters_.options.noise_suppression = false;
-  send_parameters_.options.highpass_filter = false;
-  send_parameters_.options.stereo_swapping = true;
-  SetSenderParameters(send_parameters_);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    EXPECT_FALSE(IsHighPassFilterEnabled());
-    EXPECT_TRUE(apm_config_.gain_controller1.enabled);
-    EXPECT_FALSE(apm_config_.noise_suppression.enabled);
-    EXPECT_EQ(apm_config_.noise_suppression.level, kDefaultNsLevel);
-  }
-
-  // Set options again to ensure it has no impact.
-  SetSenderParameters(send_parameters_);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    EXPECT_TRUE(apm_config_.gain_controller1.enabled);
-    EXPECT_FALSE(apm_config_.noise_suppression.enabled);
-    EXPECT_EQ(apm_config_.noise_suppression.level, kDefaultNsLevel);
-  }
-}
-
 TEST_P(WebRtcVoiceEngineTestFake, InitRecordingOnSend) {
   EXPECT_CALL(*adm_, RecordingIsInitialized()).WillOnce(Return(false));
   EXPECT_CALL(*adm_, Recording()).WillOnce(Return(false));
@@ -3236,135 +3145,6 @@
   send_channel->SetSend(true);
 }
 
-TEST_P(WebRtcVoiceEngineTestFake, SetOptionOverridesViaChannels) {
-  EXPECT_TRUE(SetupSendStream());
-  EXPECT_CALL(*adm_, BuiltInAECIsAvailable())
-      .Times(use_null_apm_ ? 4 : 8)
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(*adm_, BuiltInAGCIsAvailable())
-      .Times(use_null_apm_ ? 7 : 8)
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(*adm_, BuiltInNSIsAvailable())
-      .Times(use_null_apm_ ? 5 : 8)
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(*adm_, RecordingIsInitialized())
-      .Times(2)
-      .WillRepeatedly(Return(false));
-
-  EXPECT_CALL(*adm_, Recording()).Times(2).WillRepeatedly(Return(false));
-  EXPECT_CALL(*adm_, InitRecording()).Times(2).WillRepeatedly(Return(0));
-
-  std::unique_ptr<VoiceMediaSendChannelInterface> send_channel1(
-      engine_->CreateSendChannel(env_, &call_, MediaConfig(), AudioOptions(),
-                                 CryptoOptions()));
-  std::unique_ptr<VoiceMediaSendChannelInterface> send_channel2(
-      engine_->CreateSendChannel(env_, &call_, MediaConfig(), AudioOptions(),
-                                 CryptoOptions()));
-
-  // Have to add a stream to make SetSend work.
-  StreamParams stream1;
-  stream1.ssrcs.push_back(1);
-  send_channel1->AddSendStream(stream1);
-  StreamParams stream2;
-  stream2.ssrcs.push_back(2);
-  send_channel2->AddSendStream(stream2);
-
-  // AEC and AGC and NS
-  AudioSenderParameter parameters_options_all = send_parameters_;
-  parameters_options_all.options.echo_cancellation = true;
-  parameters_options_all.options.auto_gain_control = true;
-  parameters_options_all.options.noise_suppression = true;
-  EXPECT_TRUE(send_channel1->SetSenderParameters(parameters_options_all));
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    VerifyGainControlEnabledCorrectly();
-    EXPECT_TRUE(apm_config_.noise_suppression.enabled);
-    EXPECT_EQ(apm_config_.noise_suppression.level, kDefaultNsLevel);
-    EXPECT_EQ(parameters_options_all.options,
-              SendImplFromPointer(send_channel1.get())->options());
-    EXPECT_TRUE(send_channel2->SetSenderParameters(parameters_options_all));
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    VerifyGainControlEnabledCorrectly();
-    EXPECT_EQ(parameters_options_all.options,
-              SendImplFromPointer(send_channel2.get())->options());
-  }
-
-  // unset NS
-  AudioSenderParameter parameters_options_no_ns = send_parameters_;
-  parameters_options_no_ns.options.noise_suppression = false;
-  EXPECT_TRUE(send_channel1->SetSenderParameters(parameters_options_no_ns));
-  AudioOptions expected_options = parameters_options_all.options;
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    EXPECT_FALSE(apm_config_.noise_suppression.enabled);
-    EXPECT_EQ(apm_config_.noise_suppression.level, kDefaultNsLevel);
-    VerifyGainControlEnabledCorrectly();
-    expected_options.echo_cancellation = true;
-    expected_options.auto_gain_control = true;
-    expected_options.noise_suppression = false;
-    EXPECT_EQ(expected_options,
-              SendImplFromPointer(send_channel1.get())->options());
-  }
-
-  // unset AGC
-  AudioSenderParameter parameters_options_no_agc = send_parameters_;
-  parameters_options_no_agc.options.auto_gain_control = false;
-  EXPECT_TRUE(send_channel2->SetSenderParameters(parameters_options_no_agc));
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    EXPECT_FALSE(apm_config_.gain_controller1.enabled);
-    EXPECT_TRUE(apm_config_.noise_suppression.enabled);
-    EXPECT_EQ(apm_config_.noise_suppression.level, kDefaultNsLevel);
-    expected_options.echo_cancellation = true;
-    expected_options.auto_gain_control = false;
-    expected_options.noise_suppression = true;
-    EXPECT_EQ(expected_options,
-              SendImplFromPointer(send_channel2.get())->options());
-  }
-
-  EXPECT_TRUE(send_channel_->SetSenderParameters(parameters_options_all));
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    VerifyGainControlEnabledCorrectly();
-    EXPECT_TRUE(apm_config_.noise_suppression.enabled);
-    EXPECT_EQ(apm_config_.noise_suppression.level, kDefaultNsLevel);
-  }
-
-  send_channel1->SetSend(true);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    VerifyGainControlEnabledCorrectly();
-    EXPECT_FALSE(apm_config_.noise_suppression.enabled);
-    EXPECT_EQ(apm_config_.noise_suppression.level, kDefaultNsLevel);
-  }
-
-  send_channel2->SetSend(true);
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    EXPECT_FALSE(apm_config_.gain_controller1.enabled);
-    EXPECT_TRUE(apm_config_.noise_suppression.enabled);
-    EXPECT_EQ(apm_config_.noise_suppression.level, kDefaultNsLevel);
-  }
-
-  // Make sure settings take effect while we are sending.
-  AudioSenderParameter parameters_options_no_agc_nor_ns = send_parameters_;
-  parameters_options_no_agc_nor_ns.options.auto_gain_control = false;
-  parameters_options_no_agc_nor_ns.options.noise_suppression = false;
-  EXPECT_TRUE(
-      send_channel2->SetSenderParameters(parameters_options_no_agc_nor_ns));
-  if (!use_null_apm_) {
-    VerifyEchoCancellationSettings(/*enabled=*/true);
-    EXPECT_FALSE(apm_config_.gain_controller1.enabled);
-    EXPECT_FALSE(apm_config_.noise_suppression.enabled);
-    EXPECT_EQ(apm_config_.noise_suppression.level, kDefaultNsLevel);
-    expected_options.echo_cancellation = true;
-    expected_options.auto_gain_control = false;
-    expected_options.noise_suppression = false;
-    EXPECT_EQ(expected_options,
-              SendImplFromPointer(send_channel2.get())->options());
-  }
-}
-
 // This test verifies DSCP settings are properly applied on voice media channel.
 TEST_P(WebRtcVoiceEngineTestFake, TestSetDscpOptions) {
   EXPECT_TRUE(SetupSendStream());
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 867ec7f..50622b4 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -2865,6 +2865,7 @@
       ":connection_context",
       ":pc_test_utils",
       ":peer_connection_factory",
+      "../api:audio_options_api",
       "../api:create_modular_peer_connection_factory",
       "../api:create_peerconnection_factory",
       "../api:data_channel_interface",
@@ -2880,6 +2881,7 @@
       "../api:rtp_parameters",
       "../api:scoped_refptr",
       "../api/audio:audio_device",
+      "../api/audio:audio_processing",
       "../api/audio_codecs:builtin_audio_decoder_factory",
       "../api/audio_codecs:builtin_audio_encoder_factory",
       "../api/environment",
diff --git a/pc/local_audio_source.cc b/pc/local_audio_source.cc
index 87210b9..da6482c 100644
--- a/pc/local_audio_source.cc
+++ b/pc/local_audio_source.cc
@@ -18,16 +18,10 @@
 
 scoped_refptr<LocalAudioSource> LocalAudioSource::Create(
     const AudioOptions* audio_options) {
-  auto source = make_ref_counted<LocalAudioSource>();
-  source->Initialize(audio_options);
-  return source;
+  return make_ref_counted<LocalAudioSource>(audio_options);
 }
 
-void LocalAudioSource::Initialize(const AudioOptions* audio_options) {
-  if (!audio_options)
-    return;
-
-  options_ = *audio_options;
-}
+LocalAudioSource::LocalAudioSource(const AudioOptions* audio_options)
+    : options_(audio_options ? *audio_options : AudioOptions()) {}
 
 }  // namespace webrtc
diff --git a/pc/local_audio_source.h b/pc/local_audio_source.h
index 84f1207..4c33a93 100644
--- a/pc/local_audio_source.h
+++ b/pc/local_audio_source.h
@@ -36,13 +36,11 @@
   void RemoveSink(AudioTrackSinkInterface* sink) override {}
 
  protected:
-  LocalAudioSource() {}
+  explicit LocalAudioSource(const AudioOptions* audio_options);
   ~LocalAudioSource() override {}
 
  private:
-  void Initialize(const AudioOptions* audio_options);
-
-  AudioOptions options_;
+  const AudioOptions options_;
 };
 
 }  // namespace webrtc
diff --git a/pc/peer_connection_factory.cc b/pc/peer_connection_factory.cc
index 9e9e402..f02ab45 100644
--- a/pc/peer_connection_factory.cc
+++ b/pc/peer_connection_factory.cc
@@ -185,6 +185,14 @@
 scoped_refptr<AudioSourceInterface> PeerConnectionFactory::CreateAudioSource(
     const AudioOptions& options) {
   RTC_DCHECK(signaling_thread()->IsCurrent());
+#if !defined(WEBRTC_CHROMIUM_BUILD) && !defined(WEBRTC_WEBKIT_BUILD)
+  if (context_->media_engine() != nullptr) {
+    worker_thread()->BlockingCall([&] {
+      ConnectionContext::MediaEngineReference media_engine_ref(context_);
+      media_engine_ref.media_engine()->voice().ApplyGlobalOptions(options);
+    });
+  }
+#endif
   scoped_refptr<LocalAudioSource> source(LocalAudioSource::Create(&options));
   return source;
 }
diff --git a/pc/peer_connection_factory_unittest.cc b/pc/peer_connection_factory_unittest.cc
index 5e8a17d..7afb9f9 100644
--- a/pc/peer_connection_factory_unittest.cc
+++ b/pc/peer_connection_factory_unittest.cc
@@ -19,8 +19,10 @@
 #include <vector>
 
 #include "api/audio/audio_device.h"
+#include "api/audio/audio_processing.h"
 #include "api/audio_codecs/builtin_audio_decoder_factory.h"
 #include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "api/audio_options.h"
 #include "api/create_modular_peer_connection_factory.h"
 #include "api/create_peerconnection_factory.h"
 #include "api/data_channel_interface.h"
@@ -799,5 +801,58 @@
   EXPECT_FALSE(adm->Initialized());
 }
 
+#if !defined(WEBRTC_CHROMIUM_BUILD) && !defined(WEBRTC_WEBKIT_BUILD)
+TEST(PeerConnectionFactoryDependenciesTest,
+     CreateAudioSourceAppliesOptionsToAudioProcessing) {
+  auto ap_factory = std::make_unique<MockAudioProcessingBuilder>();
+  auto audio_processing = make_ref_counted<NiceMock<MockAudioProcessing>>();
+
+  // Capture the sequence of applied configurations to verify value-toggling
+  // and options persistence across Init/Terminate cycles.
+  std::vector<bool> aec_enabled_sequence;
+  EXPECT_CALL(*audio_processing, ApplyConfig(_))
+      .WillRepeatedly([&](const AudioProcessing::Config& config) {
+        aec_enabled_sequence.push_back(config.echo_canceller.enabled);
+      });
+  EXPECT_CALL(*ap_factory, Build).WillOnce(Return(audio_processing));
+
+  PeerConnectionFactoryDependencies pcf_dependencies;
+  pcf_dependencies.adm = FakeAudioCaptureModule::Create();
+  pcf_dependencies.audio_processing_builder = std::move(ap_factory);
+  pcf_dependencies.signaling_thread = Thread::Current();
+  pcf_dependencies.worker_thread = Thread::Current();
+  pcf_dependencies.network_thread = Thread::Current();
+  EnableMediaWithDefaults(pcf_dependencies);
+
+  scoped_refptr<PeerConnectionFactoryInterface> pcf =
+      CreateModularPeerConnectionFactory(std::move(pcf_dependencies));
+
+  // 1. First call: disable echo cancellation.
+  AudioOptions options_first;
+  options_first.echo_cancellation = false;
+  auto source1 = pcf->CreateAudioSource(options_first);
+  source1 = nullptr;  // Force reference count to 0 and Terminate().
+
+  // 2. Second call: enable echo cancellation.
+  AudioOptions options_second;
+  options_second.echo_cancellation = true;
+  auto source2 = pcf->CreateAudioSource(options_second);
+  source2 = nullptr;  // Force reference count to 0 and Terminate().
+
+  // Verify the exact sequence of applied configurations:
+  // - 1st Call Init: true (default engine option)
+  // - 1st Call Custom: false (custom option applied)
+  // - 2nd Call Init: false (properly persisted!)
+  // - 2nd Call Custom: true (new custom option applied)
+  ASSERT_EQ(aec_enabled_sequence.size(), 4u);
+  EXPECT_EQ(aec_enabled_sequence[0], true);
+  EXPECT_EQ(aec_enabled_sequence[1], false);
+  EXPECT_EQ(aec_enabled_sequence[2], false);
+  EXPECT_EQ(aec_enabled_sequence[3], true);
+
+  pcf = nullptr;
+}
+#endif
+
 }  // namespace
 }  // namespace webrtc
diff --git a/pc/rtp_sender.cc b/pc/rtp_sender.cc
index 4c1a91f..626fd26 100644
--- a/pc/rtp_sender.cc
+++ b/pc/rtp_sender.cc
@@ -1258,26 +1258,28 @@
   if (stopped_) {
     return;
   }
-  AudioOptions options;
-#if !defined(WEBRTC_CHROMIUM_BUILD) && !defined(WEBRTC_WEBKIT_BUILD)
-  // TODO(tommi): Remove this hack when we move CreateAudioSource out of
-  // PeerConnection.  This is a bit of a strange way to apply local audio
-  // options since it is also applied to all streams/channels, local or remote.
-  if (track_->enabled() && audio_track()->GetSource() &&
-      !audio_track()->GetSource()->remote()) {
-    options = audio_track()->GetSource()->options();
-  }
-#endif
-
   // `track_->enabled()` hops to the signaling thread, so call it before we hop
   // to the worker thread or else it will deadlock.
   bool track_enabled = track_->enabled();
+  const AudioOptions* options_ptr = nullptr;
+#if !defined(WEBRTC_CHROMIUM_BUILD) && !defined(WEBRTC_WEBKIT_BUILD)
+  // Stored source options (AEC, AGC, etc.) are already applied at the engine
+  // level during CreateAudioSource. However, deferred stream-level options
+  // (e.g., audio_network_adaptor, init_recording_on_send) are ignored by global
+  // engine initialization and must be explicitly applied to the channel here.
+  AudioOptions options;
+  if (track_enabled && audio_track()->GetSource() &&
+      !audio_track()->GetSource()->remote()) {
+    options = audio_track()->GetSource()->options();
+    options_ptr = &options;
+  }
+#endif
   InvalidateCache();
   bool success = worker_thread_->BlockingCall([&, ssrc = ssrc_] {
     RTC_DCHECK_RUN_ON(worker_thread_);
     return media_channel_
                ? voice_media_channel()->SetAudioSend(
-                     ssrc, track_enabled, &options, sink_adapter_.get())
+                     ssrc, track_enabled, options_ptr, sink_adapter_.get())
                : false;
   });
   if (!success) {
@@ -1297,8 +1299,7 @@
 
 void AudioRtpSender::ClearSend_w(uint32_t ssrc) {
   if (media_channel_) {
-    AudioOptions options;
-    voice_media_channel()->SetAudioSend(ssrc, false, &options, nullptr);
+    voice_media_channel()->SetAudioSend(ssrc, false, nullptr, nullptr);
   }
 }
 
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index 4f8a3e4..0fceb2b 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -505,10 +505,10 @@
           RTC_DCHECK(owned_receive_channel_);
           media_send_channel = std::move(owned_send_channel_);
           media_receive_channel = std::move(owned_receive_channel_);
-          // Apply options to the voice channels for audio and send channel for
-          // video. Note that the video options are primarily for sending.
+          // Apply options to the voice receive channel for audio and send
+          // channel for video. Note that voice send channel options are
+          // deferred to SetSend.
           if (media_type() == MediaType::AUDIO) {
-            media_send_channel->AsVoiceSendChannel()->SetOptions(audio_options);
             media_receive_channel->AsVoiceReceiveChannel()->SetOptions(
                 audio_options);
           } else if (media_type() == MediaType::VIDEO) {