diff --git a/api/media_stream_interface.h b/api/media_stream_interface.h
index fb1cc4e58e..85062ba60e 100644
--- a/api/media_stream_interface.h
+++ b/api/media_stream_interface.h
@@ -267,6 +267,11 @@ class RTC_EXPORT AudioSourceInterface : public MediaSourceInterface {
   // (for some of the settings this approach is broken, e.g. setting
   // audio network adaptation on the source is the wrong layer of abstraction).
   virtual const AudioOptions options() const;
+
+  // Returns true if this source delivers audio externally (via AddSink),
+  // bypassing the ADM/AudioState audio distribution path.
+  // When true, AudioSendStream should not register with AudioState.
+  virtual bool is_external_source() const { return false; }
 };
 
 // Interface of the audio processor used by the audio track to collect
diff --git a/audio/audio_send_stream.cc b/audio/audio_send_stream.cc
index 76156ce830..10b59d3ff6 100644
--- a/audio/audio_send_stream.cc
+++ b/audio/audio_send_stream.cc
@@ -373,8 +373,13 @@ void AudioSendStream::Start() {
   }
   channel_send_->StartSend();
   sending_ = true;
-  audio_state()->AddSendingStream(this, encoder_sample_rate_hz_,
-                                  encoder_num_channels_);
+  // Only register with AudioState if not using external source.
+  // External sources (like NativeAudioSource) deliver audio directly via AddSink,
+  // so we don't want AudioState to also send device audio to this stream.
+  if (!config_.external_source) {
+    audio_state()->AddSendingStream(this, encoder_sample_rate_hz_,
+                                    encoder_num_channels_);
+  }
 }
 
 void AudioSendStream::Stop() {
@@ -386,7 +391,10 @@ void AudioSendStream::Stop() {
   RemoveBitrateObserver();
   channel_send_->StopSend();
   sending_ = false;
-  audio_state()->RemoveSendingStream(this);
+  // Only unregister if we registered (when not using external source).
+  if (!config_.external_source) {
+    audio_state()->RemoveSendingStream(this);
+  }
 }
 
 void AudioSendStream::SendAudioData(std::unique_ptr<AudioFrame> audio_frame) {
diff --git a/call/audio_send_stream.h b/call/audio_send_stream.h
index 84341b5cb1..9359777bc9 100644
--- a/call/audio_send_stream.h
+++ b/call/audio_send_stream.h
@@ -178,6 +178,12 @@ class AudioSendStream : public AudioSender {
     // An optional frame transformer used by insertable streams to transform
     // encoded frames.
     scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer;
+
+    // When true, this stream uses an external audio source (not ADM).
+    // AudioState will NOT send device-captured audio to this stream.
+    // Audio is delivered directly via the source's AddSink mechanism.
+    // This prevents mixing of device audio with externally-sourced audio.
+    bool external_source = false;
   };
 
   virtual ~AudioSendStream() = default;
diff --git a/media/base/audio_source.h b/media/base/audio_source.h
index 04a7d19dfa..9f513f3c75 100644
--- a/media/base/audio_source.h
+++ b/media/base/audio_source.h
@@ -49,6 +49,10 @@ class AudioSource {
   // to the source at a time.
   virtual void SetSink(Sink* sink) = 0;
 
+  // Returns true if this source delivers audio externally (bypassing ADM).
+  // When true, AudioSendStream should not register with AudioState.
+  virtual bool is_external_source() const { return false; }
+
  protected:
   virtual ~AudioSource() {}
 };
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index 762f9d584c..4ce07ddc9d 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -1017,6 +1017,14 @@ class WebRtcVoiceSendChannel::WebRtcAudioSendStream : public AudioSource::Sink {
       RTC_DCHECK(source_ == source);
       return;
     }
+
+    // Check if this is an external audio source (delivers audio via AddSink).
+    // If so, mark the config so AudioState doesn't send device audio to this
+    // stream. This must be done before UpdateSendState() calls Start().
+    if (source->is_external_source() && !config_.external_source) {
+      config_.external_source = true;
+      stream_->Reconfigure(config_, nullptr);
+    }
     source->SetSink(this);
     source_ = source;
     UpdateSendState();
diff --git a/pc/rtp_sender.cc b/pc/rtp_sender.cc
index d5edbbf0ed..c14ddfe868 100644
--- a/pc/rtp_sender.cc
+++ b/pc/rtp_sender.cc
@@ -786,6 +786,13 @@ void AudioRtpSender::SetSend() {
   RTC_DCHECK_RUN_ON(signaling_thread_);
   RTC_DCHECK(!stopped_);
   RTC_DCHECK(can_send_track());
+
+  // Propagate is_external_source from AudioSourceInterface to the sink adapter.
+  // This ensures the voice engine knows not to mix ADM audio with external sources.
+  if (audio_track()->GetSource() && audio_track()->GetSource()->is_external_source()) {
+    sink_adapter_->set_is_external_source(true);
+  }
+
   if (!media_channel_) {
     RTC_LOG(LS_ERROR) << "SetAudioSend: No audio channel exists.";
     return;
diff --git a/pc/rtp_sender.h b/pc/rtp_sender.h
index eaffd4ef0f..d0489df9e7 100644
--- a/pc/rtp_sender.h
+++ b/pc/rtp_sender.h
@@ -307,6 +307,12 @@ class LocalAudioSinkAdapter : public AudioTrackSinkInterface,
   LocalAudioSinkAdapter();
   virtual ~LocalAudioSinkAdapter();
 
+  // Set whether the original AudioSourceInterface is an external source.
+  // This propagates the is_external_source state from AudioSourceInterface
+  // to this AudioSource adapter.
+  void set_is_external_source(bool value) { is_external_source_ = value; }
+  bool is_external_source() const override { return is_external_source_; }
+
  private:
   // AudioSinkInterface implementation.
   void OnData(const void* audio_data,
@@ -337,6 +343,7 @@ class LocalAudioSinkAdapter : public AudioTrackSinkInterface,
   // Critical section protecting `sink_`.
   Mutex lock_;
   int num_preferred_channels_ = -1;
+  bool is_external_source_ = false;
 };
 
 class AudioRtpSender : public DtmfProviderInterface, public RtpSenderBase {
