Enable End-to-End Encrypted Audio Payloads.

This change integrates the FrameDecryptorInterface and the FrameEncryptorInterface into
the audio media path. If a FrameEncryptorInterface is set on an outgoing audio RTPSender
then each outgoing audio payload will first pass through the provided FrameEncryptor which
will have a chance to modify the payload contents for the purposes of encryption.

If a FrameDecryptorInterface is set on an incoming audio RtpReceiver then each incoming
audio payload will first pass through the provided FrameDecryptor which have a chance to
modify the payload contents for the purpose of decryption.

While AEAD is supported by the FrameDecryptor/FrameEncryptor interfaces this CL does not
use it and so it is left as null.

Bug: webrtc:9681
Change-Id: Ic383a9dce280528739f9d271357c2220e0a0dccf
Reviewed-on: https://webrtc-review.googlesource.com/c/101702
Commit-Queue: Benjamin Wright <benwright@webrtc.org>
Reviewed-by: Fredrik Solenberg <solenberg@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Emad Omara <emadomara@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25001}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 007879b..5b21334 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -446,6 +446,23 @@
     ]
   }
 
+  rtc_source_set("fake_frame_crypto") {
+    testonly = true
+    sources = [
+      "test/fake_frame_decryptor.cc",
+      "test/fake_frame_decryptor.h",
+      "test/fake_frame_encryptor.cc",
+      "test/fake_frame_encryptor.h",
+    ]
+    deps = [
+      ":array_view",
+      ":libjingle_peerconnection_api",
+      "..:webrtc_common",
+      "../rtc_base:checks",
+      "../rtc_base:rtc_base_approved",
+    ]
+  }
+
   rtc_source_set("mock_peerconnectioninterface") {
     testonly = true
     sources = [
diff --git a/api/test/fake_frame_decryptor.cc b/api/test/fake_frame_decryptor.cc
new file mode 100644
index 0000000..432664a
--- /dev/null
+++ b/api/test/fake_frame_decryptor.cc
@@ -0,0 +1,69 @@
+/*
+ *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/test/fake_frame_decryptor.h"
+#include <vector>
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+FakeFrameDecryptor::FakeFrameDecryptor(uint8_t fake_key,
+                                       uint8_t expected_postfix_byte)
+    : fake_key_(fake_key), expected_postfix_byte_(expected_postfix_byte) {}
+
+int FakeFrameDecryptor::Decrypt(cricket::MediaType media_type,
+                                const std::vector<uint32_t>& csrcs,
+                                rtc::ArrayView<const uint8_t> additional_data,
+                                rtc::ArrayView<const uint8_t> encrypted_frame,
+                                rtc::ArrayView<uint8_t> frame,
+                                size_t* bytes_written) {
+  if (fail_decryption_) {
+    return 1;
+  }
+
+  RTC_CHECK_EQ(frame.size() + 1, encrypted_frame.size());
+  for (size_t i = 0; i < frame.size(); i++) {
+    frame[i] ^= fake_key_;
+  }
+
+  if (encrypted_frame[frame.size()] != expected_postfix_byte_) {
+    return 1;
+  }
+
+  return 0;
+}
+
+size_t FakeFrameDecryptor::GetMaxPlaintextByteSize(
+    cricket::MediaType media_type,
+    size_t encrypted_frame_size) {
+  return encrypted_frame_size - 1;
+}
+
+void FakeFrameDecryptor::SetFakeKey(uint8_t fake_key) {
+  fake_key_ = fake_key;
+}
+
+uint8_t FakeFrameDecryptor::GetFakeKey() const {
+  return fake_key_;
+}
+
+void FakeFrameDecryptor::SetExpectedPostfixByte(uint8_t expected_postfix_byte) {
+  expected_postfix_byte_ = expected_postfix_byte;
+}
+
+uint8_t FakeFrameDecryptor::GetExpectedPostfixByte() const {
+  return expected_postfix_byte_;
+}
+
+void FakeFrameDecryptor::SetFailDecryption(bool fail_decryption) {
+  fail_decryption_ = fail_decryption;
+}
+
+}  // namespace webrtc
diff --git a/api/test/fake_frame_decryptor.h b/api/test/fake_frame_decryptor.h
new file mode 100644
index 0000000..b945def
--- /dev/null
+++ b/api/test/fake_frame_decryptor.h
@@ -0,0 +1,62 @@
+/*
+ *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_TEST_FAKE_FRAME_DECRYPTOR_H_
+#define API_TEST_FAKE_FRAME_DECRYPTOR_H_
+
+#include <vector>
+
+#include "api/crypto/framedecryptorinterface.h"
+#include "rtc_base/refcountedobject.h"
+
+namespace webrtc {
+
+// The FakeFrameDecryptor is a TEST ONLY fake implementation of the
+// FrameDecryptorInterface. It is constructed with a simple single digit key and
+// a fixed postfix byte. This is just to validate that the core code works
+// as expected.
+class FakeFrameDecryptor
+    : public rtc::RefCountedObject<FrameDecryptorInterface> {
+ public:
+  // Provide a key (0,255) and some postfix byte (0,255) this should match the
+  // byte you expect from the FakeFrameEncryptor.
+  explicit FakeFrameDecryptor(uint8_t fake_key = 1,
+                              uint8_t expected_postfix_byte = 255);
+
+  // FrameDecryptorInterface implementation
+  int Decrypt(cricket::MediaType media_type,
+              const std::vector<uint32_t>& csrcs,
+              rtc::ArrayView<const uint8_t> additional_data,
+              rtc::ArrayView<const uint8_t> encrypted_frame,
+              rtc::ArrayView<uint8_t> frame,
+              size_t* bytes_written) override;
+
+  size_t GetMaxPlaintextByteSize(cricket::MediaType media_type,
+                                 size_t encrypted_frame_size) override;
+
+  void SetFakeKey(uint8_t fake_key);
+
+  uint8_t GetFakeKey() const;
+
+  void SetExpectedPostfixByte(uint8_t expected_postfix_byte);
+
+  uint8_t GetExpectedPostfixByte() const;
+
+  void SetFailDecryption(bool fail_decryption);
+
+ private:
+  uint8_t fake_key_ = 0;
+  uint8_t expected_postfix_byte_ = 0;
+  bool fail_decryption_ = false;
+};
+
+}  // namespace webrtc
+
+#endif  // API_TEST_FAKE_FRAME_DECRYPTOR_H_
diff --git a/api/test/fake_frame_encryptor.cc b/api/test/fake_frame_encryptor.cc
new file mode 100644
index 0000000..013058f
--- /dev/null
+++ b/api/test/fake_frame_encryptor.cc
@@ -0,0 +1,65 @@
+/*
+ *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/test/fake_frame_encryptor.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+FakeFrameEncryptor::FakeFrameEncryptor(uint8_t fake_key, uint8_t postfix_byte)
+    : fake_key_(fake_key), postfix_byte_(postfix_byte) {}
+
+// FrameEncryptorInterface implementation
+int FakeFrameEncryptor::Encrypt(cricket::MediaType media_type,
+                                uint32_t ssrc,
+                                rtc::ArrayView<const uint8_t> additional_data,
+                                rtc::ArrayView<const uint8_t> frame,
+                                rtc::ArrayView<uint8_t> encrypted_frame,
+                                size_t* bytes_written) {
+  // Useful if you want to test failure cases.
+  if (fail_encryption_) {
+    return 1;
+  }
+
+  RTC_CHECK_EQ(frame.size() + 1, encrypted_frame.size());
+  for (size_t i = 0; i < frame.size(); i++) {
+    encrypted_frame[i] ^= fake_key_;
+  }
+  encrypted_frame[frame.size()] = postfix_byte_;
+  *bytes_written = encrypted_frame.size();
+  return 0;
+}
+
+size_t FakeFrameEncryptor::GetMaxCiphertextByteSize(
+    cricket::MediaType media_type,
+    size_t frame_size) {
+  return frame_size + 1;
+}
+
+void FakeFrameEncryptor::SetFakeKey(uint8_t fake_key) {
+  fake_key_ = fake_key;
+}
+
+uint8_t FakeFrameEncryptor::GetFakeKey() const {
+  return fake_key_;
+}
+
+void FakeFrameEncryptor::SetPostfixByte(uint8_t postfix_byte) {
+  postfix_byte_ = postfix_byte;
+}
+
+uint8_t FakeFrameEncryptor::GetPostfixByte() const {
+  return postfix_byte_;
+}
+
+void FakeFrameEncryptor::SetFailEncryption(bool fail_encryption) {
+  fail_encryption_ = fail_encryption;
+}
+
+}  // namespace webrtc
diff --git a/api/test/fake_frame_encryptor.h b/api/test/fake_frame_encryptor.h
new file mode 100644
index 0000000..61ae938
--- /dev/null
+++ b/api/test/fake_frame_encryptor.h
@@ -0,0 +1,58 @@
+/*
+ *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_TEST_FAKE_FRAME_ENCRYPTOR_H_
+#define API_TEST_FAKE_FRAME_ENCRYPTOR_H_
+
+#include "api/crypto/frameencryptorinterface.h"
+#include "rtc_base/refcountedobject.h"
+
+namespace webrtc {
+
+// The FakeFrameEncryptor is a TEST ONLY fake implementation of the
+// FrameEncryptorInterface. It is constructed with a simple single digit key and
+// a fixed postfix byte. This is just to validate that the core code works
+// as expected.
+class FakeFrameEncryptor
+    : public rtc::RefCountedObject<FrameEncryptorInterface> {
+ public:
+  // Provide a key (0,255) and some postfix byte (0,255).
+  explicit FakeFrameEncryptor(uint8_t fake_key = 1, uint8_t postfix_byte = 255);
+
+  // FrameEncryptorInterface implementation
+  int Encrypt(cricket::MediaType media_type,
+              uint32_t ssrc,
+              rtc::ArrayView<const uint8_t> additional_data,
+              rtc::ArrayView<const uint8_t> frame,
+              rtc::ArrayView<uint8_t> encrypted_frame,
+              size_t* bytes_written) override;
+
+  size_t GetMaxCiphertextByteSize(cricket::MediaType media_type,
+                                  size_t frame_size) override;
+
+  void SetFakeKey(uint8_t fake_key);
+
+  uint8_t GetFakeKey() const;
+
+  void SetPostfixByte(uint8_t expected_postfix_byte);
+
+  uint8_t GetPostfixByte() const;
+
+  void SetFailEncryption(bool fail_encryption);
+
+ private:
+  uint8_t fake_key_ = 0;
+  uint8_t postfix_byte_ = 0;
+  bool fail_encryption_ = false;
+};
+
+}  // namespace webrtc
+
+#endif  // API_TEST_FAKE_FRAME_ENCRYPTOR_H_
diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc
index b9faea5..7e473ed 100644
--- a/audio/audio_receive_stream.cc
+++ b/audio/audio_receive_stream.cc
@@ -76,7 +76,7 @@
           nullptr /* RtcpRttStats */, event_log, config.rtp.remote_ssrc,
           config.jitter_buffer_max_packets,
           config.jitter_buffer_fast_accelerate, config.decoder_factory,
-          config.codec_pair_id));
+          config.codec_pair_id, config.frame_decryptor));
 }
 }  // namespace
 
diff --git a/audio/audio_send_stream.cc b/audio/audio_send_stream.cc
index 06be261..e82264a 100644
--- a/audio/audio_send_stream.cc
+++ b/audio/audio_send_stream.cc
@@ -50,10 +50,12 @@
     rtc::TaskQueue* worker_queue,
     ProcessThread* module_process_thread,
     RtcpRttStats* rtcp_rtt_stats,
-    RtcEventLog* event_log) {
+    RtcEventLog* event_log,
+    FrameEncryptorInterface* frame_encryptor) {
   return absl::make_unique<voe::ChannelSendProxy>(
       absl::make_unique<voe::ChannelSend>(worker_queue, module_process_thread,
-                                          rtcp_rtt_stats, event_log));
+                                          rtcp_rtt_stats, event_log,
+                                          frame_encryptor));
 }
 }  // namespace
 
@@ -103,7 +105,8 @@
                       CreateChannelAndProxy(worker_queue,
                                             module_process_thread,
                                             rtcp_rtt_stats,
-                                            event_log)) {}
+                                            event_log,
+                                            config.frame_encryptor)) {}
 
 AudioSendStream::AudioSendStream(
     const webrtc::AudioSendStream::Config& config,
@@ -227,6 +230,11 @@
         stream->timed_send_transport_adapter_.get());
   }
 
+  // Enable the frame encryptor if a new frame encryptor has been provided.
+  if (first_time || new_config.frame_encryptor != old_config.frame_encryptor) {
+    channel_proxy->SetFrameEncryptor(new_config.frame_encryptor);
+  }
+
   const ExtensionIds old_ids = FindExtensionIds(old_config.rtp.extensions);
   const ExtensionIds new_ids = FindExtensionIds(new_config.rtp.extensions);
   // Audio level indication
diff --git a/audio/audio_send_stream_unittest.cc b/audio/audio_send_stream_unittest.cc
index 0ed1ac1..0a954f8 100644
--- a/audio/audio_send_stream_unittest.cc
+++ b/audio/audio_send_stream_unittest.cc
@@ -196,6 +196,7 @@
     EXPECT_CALL(*channel_proxy_, SetLocalSSRC(kSsrc)).Times(1);
     EXPECT_CALL(*channel_proxy_, SetRTCP_CNAME(StrEq(kCName))).Times(1);
     EXPECT_CALL(*channel_proxy_, SetNACKStatus(true, 10)).Times(1);
+    EXPECT_CALL(*channel_proxy_, SetFrameEncryptor(nullptr)).Times(1);
     EXPECT_CALL(*channel_proxy_,
                 SetSendAudioLevelIndicationStatus(true, kAudioLevelId))
         .Times(1);
diff --git a/audio/channel_receive.cc b/audio/channel_receive.cc
index 784e4a7..26213cc 100644
--- a/audio/channel_receive.cc
+++ b/audio/channel_receive.cc
@@ -229,7 +229,8 @@
     size_t jitter_buffer_max_packets,
     bool jitter_buffer_fast_playout,
     rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
-    absl::optional<AudioCodecPairId> codec_pair_id)
+    absl::optional<AudioCodecPairId> codec_pair_id,
+    FrameDecryptorInterface* frame_decryptor)
     : event_log_(rtc_event_log),
       rtp_receive_statistics_(
           ReceiveStatistics::Create(Clock::GetRealTimeClock())),
@@ -245,7 +246,8 @@
       _audioDeviceModulePtr(audio_device_module),
       _transportPtr(NULL),
       _outputGain(1.0f),
-      associated_send_channel_(nullptr) {
+      associated_send_channel_(nullptr),
+      frame_decryptor_(frame_decryptor) {
   RTC_DCHECK(module_process_thread);
   RTC_DCHECK(audio_device_module);
   AudioCodingModule::Config acm_config;
@@ -425,7 +427,38 @@
   WebRtcRTPHeader webrtc_rtp_header = {};
   webrtc_rtp_header.header = header;
 
-  const size_t payload_data_length = payload_length - header.paddingLength;
+  size_t payload_data_length = payload_length - header.paddingLength;
+
+  // E2EE Custom Audio Frame Decryption (This is optional).
+  // Keep this buffer around for the lifetime of the OnReceivedPayloadData call.
+  rtc::Buffer decrypted_audio_payload;
+  if (frame_decryptor_ != nullptr) {
+    size_t max_plaintext_size = frame_decryptor_->GetMaxPlaintextByteSize(
+        cricket::MEDIA_TYPE_AUDIO, payload_length);
+    decrypted_audio_payload.SetSize(max_plaintext_size);
+
+    size_t bytes_written = 0;
+    std::vector<uint32_t> csrcs(header.arrOfCSRCs,
+                                header.arrOfCSRCs + header.numCSRCs);
+    int decrypt_status = frame_decryptor_->Decrypt(
+        cricket::MEDIA_TYPE_AUDIO, csrcs,
+        /*additional_data=*/nullptr,
+        rtc::ArrayView<const uint8_t>(payload, payload_data_length),
+        decrypted_audio_payload, &bytes_written);
+
+    // In this case just interpret the failure as a silent frame.
+    if (decrypt_status != 0) {
+      bytes_written = 0;
+    }
+
+    // Resize the decrypted audio payload to the number of bytes actually
+    // written.
+    decrypted_audio_payload.SetSize(bytes_written);
+    // Update the final payload.
+    payload = decrypted_audio_payload.data();
+    payload_data_length = decrypted_audio_payload.size();
+  }
+
   if (payload_data_length == 0) {
     webrtc_rtp_header.frameType = kEmptyFrame;
     return OnReceivedPayloadData(nullptr, 0, &webrtc_rtp_header);
diff --git a/audio/channel_receive.h b/audio/channel_receive.h
index 20198c4..2e089b7 100644
--- a/audio/channel_receive.h
+++ b/audio/channel_receive.h
@@ -42,6 +42,7 @@
 namespace webrtc {
 
 class AudioDeviceModule;
+class FrameDecryptorInterface;
 class PacketRouter;
 class ProcessThread;
 class RateLimiter;
@@ -112,7 +113,8 @@
                  size_t jitter_buffer_max_packets,
                  bool jitter_buffer_fast_playout,
                  rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
-                 absl::optional<AudioCodecPairId> codec_pair_id);
+                 absl::optional<AudioCodecPairId> codec_pair_id,
+                 FrameDecryptorInterface* frame_decryptor);
   virtual ~ChannelReceive();
 
   void SetSink(AudioSinkInterface* sink);
@@ -263,6 +265,9 @@
   PacketRouter* packet_router_ = nullptr;
 
   rtc::ThreadChecker construction_thread_;
+
+  // E2EE Audio Frame Decryption
+  FrameDecryptorInterface* frame_decryptor_ = nullptr;
 };
 
 }  // namespace voe
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
index 0c9328f..d3748b3 100644
--- a/audio/channel_send.cc
+++ b/audio/channel_send.cc
@@ -19,6 +19,7 @@
 
 #include "absl/memory/memory.h"
 #include "api/array_view.h"
+#include "api/crypto/frameencryptorinterface.h"
 #include "audio/utility/audio_frame_operations.h"
 #include "call/rtp_transport_controller_send_interface.h"
 #include "logging/rtc_event_log/events/rtc_event_audio_playout.h"
@@ -260,6 +261,35 @@
     _rtpRtcpModule->SetAudioLevel(rms_level_.Average());
   }
 
+  // E2EE Custom Audio Frame Encryption (This is optional).
+  // Keep this buffer around for the lifetime of the send call.
+  rtc::Buffer encrypted_audio_payload;
+  if (frame_encryptor_ != nullptr) {
+    // TODO(benwright@webrtc.org) - Allocate enough to always encrypt inline.
+    // Allocate a buffer to hold the maximum possible encrypted payload.
+    size_t max_ciphertext_size = frame_encryptor_->GetMaxCiphertextByteSize(
+        cricket::MEDIA_TYPE_AUDIO, payloadSize);
+    encrypted_audio_payload.SetSize(max_ciphertext_size);
+
+    // Encrypt the audio payload into the buffer.
+    size_t bytes_written = 0;
+    int encrypt_status = frame_encryptor_->Encrypt(
+        cricket::MEDIA_TYPE_AUDIO, _rtpRtcpModule->SSRC(),
+        /*additional_data=*/nullptr,
+        rtc::ArrayView<const uint8_t>(payloadData, payloadSize),
+        encrypted_audio_payload, &bytes_written);
+    if (encrypt_status != 0) {
+      RTC_DLOG(LS_ERROR) << "Channel::SendData() failed encrypt audio payload: "
+                         << encrypt_status;
+      return -1;
+    }
+    // Resize the buffer to the exact number of bytes actually used.
+    encrypted_audio_payload.SetSize(bytes_written);
+    // Rewrite the payloadData and size to the new encrypted payload.
+    payloadData = encrypted_audio_payload.data();
+    payloadSize = encrypted_audio_payload.size();
+  }
+
   // Push data from ACM to RTP/RTCP-module to deliver audio frame for
   // packetization.
   // This call will trigger Transport::SendPacket() from the RTP/RTCP module.
@@ -322,7 +352,8 @@
 ChannelSend::ChannelSend(rtc::TaskQueue* encoder_queue,
                          ProcessThread* module_process_thread,
                          RtcpRttStats* rtcp_rtt_stats,
-                         RtcEventLog* rtc_event_log)
+                         RtcEventLog* rtc_event_log,
+                         FrameEncryptorInterface* frame_encryptor)
     : event_log_(rtc_event_log),
       _timeStamp(0),  // This is just an offset, RTP module will add it's own
                       // random offset
@@ -342,7 +373,8 @@
                                                    kMaxRetransmissionWindowMs)),
       use_twcc_plr_for_ana_(
           webrtc::field_trial::FindFullName("UseTwccPlrForAna") == "Enabled"),
-      encoder_queue_(encoder_queue) {
+      encoder_queue_(encoder_queue),
+      frame_encryptor_(frame_encryptor) {
   RTC_DCHECK(module_process_thread);
   RTC_DCHECK(encoder_queue);
   audio_coding_.reset(AudioCodingModule::Create(AudioCodingModule::Config()));
@@ -949,5 +981,16 @@
   return rtt;
 }
 
+void ChannelSend::SetFrameEncryptor(FrameEncryptorInterface* frame_encryptor) {
+  rtc::CritScope cs(&encoder_queue_lock_);
+  if (encoder_queue_is_active_) {
+    encoder_queue_->PostTask([this, frame_encryptor]() {
+      this->frame_encryptor_ = frame_encryptor;
+    });
+  } else {
+    frame_encryptor_ = frame_encryptor;
+  }
+}
+
 }  // namespace voe
 }  // namespace webrtc
diff --git a/audio/channel_send.h b/audio/channel_send.h
index 4569201..ef92f8e 100644
--- a/audio/channel_send.h
+++ b/audio/channel_send.h
@@ -37,6 +37,7 @@
 
 namespace webrtc {
 
+class FrameEncryptorInterface;
 class PacketRouter;
 class ProcessThread;
 class RateLimiter;
@@ -118,7 +119,8 @@
   ChannelSend(rtc::TaskQueue* encoder_queue,
               ProcessThread* module_process_thread,
               RtcpRttStats* rtcp_rtt_stats,
-              RtcEventLog* rtc_event_log);
+              RtcEventLog* rtc_event_log,
+              FrameEncryptorInterface* frame_encryptor);
 
   virtual ~ChannelSend();
 
@@ -222,6 +224,9 @@
 
   int64_t GetRTT() const;
 
+  // E2EE Custom Audio Frame Encryption
+  void SetFrameEncryptor(FrameEncryptorInterface* frame_encryptor);
+
  private:
   class ProcessAndEncodeAudioTask;
 
@@ -290,6 +295,9 @@
   rtc::CriticalSection encoder_queue_lock_;
   bool encoder_queue_is_active_ RTC_GUARDED_BY(encoder_queue_lock_) = false;
   rtc::TaskQueue* encoder_queue_ = nullptr;
+
+  // E2EE Audio Frame Encryption
+  FrameEncryptorInterface* frame_encryptor_ = nullptr;
 };
 
 }  // namespace voe
diff --git a/audio/channel_send_proxy.cc b/audio/channel_send_proxy.cc
index a4d8b69..8091bdc 100644
--- a/audio/channel_send_proxy.cc
+++ b/audio/channel_send_proxy.cc
@@ -197,5 +197,11 @@
   return channel_.get();
 }
 
+void ChannelSendProxy::SetFrameEncryptor(
+    FrameEncryptorInterface* frame_encryptor) {
+  RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+  channel_->SetFrameEncryptor(frame_encryptor);
+}
+
 }  // namespace voe
 }  // namespace webrtc
diff --git a/audio/channel_send_proxy.h b/audio/channel_send_proxy.h
index 754f9f6..1b8b4a0 100644
--- a/audio/channel_send_proxy.h
+++ b/audio/channel_send_proxy.h
@@ -23,6 +23,7 @@
 
 namespace webrtc {
 
+class FrameEncryptorInterface;
 class RtcpBandwidthObserver;
 class RtpRtcp;
 class RtpTransportControllerSendInterface;
@@ -84,6 +85,9 @@
   // Needed by ChannelReceiveProxy::AssociateSendChannel.
   virtual ChannelSend* GetChannel() const;
 
+  // E2EE Custom Audio Frame Encryption (Optional)
+  virtual void SetFrameEncryptor(FrameEncryptorInterface* frame_encryptor);
+
  private:
   // Thread checkers document and lock usage of some methods on voe::Channel to
   // specific threads we know about. The goal is to eventually split up
diff --git a/audio/mock_voe_channel_proxy.h b/audio/mock_voe_channel_proxy.h
index 0ae3cdc..88a50ea 100644
--- a/audio/mock_voe_channel_proxy.h
+++ b/audio/mock_voe_channel_proxy.h
@@ -105,6 +105,8 @@
                void(float recoverable_packet_loss_rate));
   MOCK_METHOD0(StartSend, void());
   MOCK_METHOD0(StopSend, void());
+  MOCK_METHOD1(SetFrameEncryptor,
+               void(FrameEncryptorInterface* frame_encryptor));
 };
 }  // namespace test
 }  // namespace webrtc
diff --git a/call/audio_receive_stream.h b/call/audio_receive_stream.h
index 9595a29..9c890b1 100644
--- a/call/audio_receive_stream.h
+++ b/call/audio_receive_stream.h
@@ -27,6 +27,7 @@
 
 namespace webrtc {
 class AudioSinkInterface;
+class FrameDecryptorInterface;
 
 class AudioReceiveStream {
  public:
@@ -120,6 +121,11 @@
     rtc::scoped_refptr<AudioDecoderFactory> decoder_factory;
 
     absl::optional<AudioCodecPairId> codec_pair_id;
+
+    // An optional custom frame decryptor that allows the entire frame to be
+    // decrypted in whatever way the caller choses. This is not required by
+    // default.
+    rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor;
   };
 
   // Reconfigure the stream according to the Configuration.
diff --git a/call/audio_send_stream.h b/call/audio_send_stream.h
index 60909ae..61e2531 100644
--- a/call/audio_send_stream.h
+++ b/call/audio_send_stream.h
@@ -21,6 +21,7 @@
 #include "api/audio_codecs/audio_encoder_factory.h"
 #include "api/audio_codecs/audio_format.h"
 #include "api/call/transport.h"
+#include "api/crypto/frameencryptorinterface.h"
 #include "api/rtpparameters.h"
 #include "call/rtp_config.h"
 #include "modules/audio_processing/include/audio_processing_statistics.h"
@@ -128,6 +129,11 @@
 
     // Track ID as specified during track creation.
     std::string track_id;
+
+    // An optional custom frame encryptor that allows the entire frame to be
+    // encryptor in whatever way the caller choses. This is not required by
+    // default.
+    rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor;
   };
 
   virtual ~AudioSendStream() = default;
diff --git a/media/base/mediachannel.cc b/media/base/mediachannel.cc
index 304d7f8..cba3be3 100644
--- a/media/base/mediachannel.cc
+++ b/media/base/mediachannel.cc
@@ -15,6 +15,13 @@
 VideoOptions::VideoOptions() = default;
 VideoOptions::~VideoOptions() = default;
 
+MediaChannel::MediaChannel(const MediaConfig& config)
+    : enable_dscp_(config.enable_dscp), network_interface_(NULL) {}
+
+MediaChannel::MediaChannel() : enable_dscp_(false), network_interface_(NULL) {}
+
+MediaChannel::~MediaChannel() {}
+
 void MediaChannel::SetInterface(NetworkInterface* iface) {
   rtc::CritScope cs(&network_interface_crit_);
   network_interface_ = iface;
@@ -30,13 +37,15 @@
 }
 
 void MediaChannel::SetFrameEncryptor(
-    webrtc::FrameEncryptorInterface* frame_encryptor) {
-  frame_encryptor_ = frame_encryptor;
+    uint32_t ssrc,
+    rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor) {
+  // Placeholder should be pure virtual once internal supports it.
 }
 
 void MediaChannel::SetFrameDecryptor(
-    webrtc::FrameDecryptorInterface* frame_decryptor) {
-  frame_decryptor_ = frame_decryptor;
+    uint32_t ssrc,
+    rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) {
+  // Placeholder should be pure virtual once internal supports it.
 }
 
 MediaSenderInfo::MediaSenderInfo() = default;
diff --git a/media/base/mediachannel.h b/media/base/mediachannel.h
index 98b36ac..ff3368c 100644
--- a/media/base/mediachannel.h
+++ b/media/base/mediachannel.h
@@ -179,10 +179,9 @@
     virtual ~NetworkInterface() {}
   };
 
-  explicit MediaChannel(const MediaConfig& config)
-      : enable_dscp_(config.enable_dscp), network_interface_(NULL) {}
-  MediaChannel() : enable_dscp_(false), network_interface_(NULL) {}
-  ~MediaChannel() override {}
+  explicit MediaChannel(const MediaConfig& config);
+  MediaChannel();
+  ~MediaChannel() override;
 
   // Sets the abstract interface class for sending RTP/RTCP data.
   virtual void SetInterface(NetworkInterface* iface);
@@ -219,13 +218,17 @@
   // Set the frame encryptor to use on all outgoing frames. This is optional.
   // This pointers lifetime is managed by the set of RtpSender it is attached
   // to.
+  // TODO(benwright) make pure virtual once internal supports it.
   virtual void SetFrameEncryptor(
-      webrtc::FrameEncryptorInterface* frame_encryptor);
+      uint32_t ssrc,
+      rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor);
   // Set the frame decryptor to use on all incoming frames. This is optional.
   // This pointers lifetimes is managed by the set of RtpReceivers it is
   // attached to.
+  // TODO(benwright) make pure virtual once internal supports it.
   virtual void SetFrameDecryptor(
-      webrtc::FrameDecryptorInterface* frame_decryptor);
+      uint32_t ssrc,
+      rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor);
 
   // Base method to send packet using NetworkInterface.
   bool SendPacket(rtc::CopyOnWriteBuffer* packet,
@@ -281,10 +284,6 @@
   // of network_interface_ object.
   rtc::CriticalSection network_interface_crit_;
   NetworkInterface* network_interface_;
-
- protected:
-  webrtc::FrameEncryptorInterface* frame_encryptor_ = nullptr;
-  webrtc::FrameDecryptorInterface* frame_decryptor_ = nullptr;
 };
 
 // The stats information is structured as follows:
diff --git a/media/engine/webrtcvoiceengine.cc b/media/engine/webrtcvoiceengine.cc
index bbe48a2..6b14c23 100644
--- a/media/engine/webrtcvoiceengine.cc
+++ b/media/engine/webrtcvoiceengine.cc
@@ -719,7 +719,8 @@
       webrtc::Call* call,
       webrtc::Transport* send_transport,
       const rtc::scoped_refptr<webrtc::AudioEncoderFactory>& encoder_factory,
-      const absl::optional<webrtc::AudioCodecPairId> codec_pair_id)
+      const absl::optional<webrtc::AudioCodecPairId> codec_pair_id,
+      rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor)
       : call_(call),
         config_(send_transport),
         send_side_bwe_with_overhead_(
@@ -736,6 +737,7 @@
     config_.encoder_factory = encoder_factory;
     config_.codec_pair_id = codec_pair_id;
     config_.track_id = track_id;
+    config_.frame_encryptor = frame_encryptor;
     rtp_parameters_.encodings[0].ssrc = ssrc;
     rtp_parameters_.rtcp.cname = c_name;
     rtp_parameters_.header_extensions = extensions;
@@ -775,6 +777,13 @@
     ReconfigureAudioSendStream();
   }
 
+  void SetFrameEncryptor(
+      rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor) {
+    RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+    config_.frame_encryptor = frame_encryptor;
+    ReconfigureAudioSendStream();
+  }
+
   void SetAudioNetworkAdaptorConfig(
       const absl::optional<std::string>& audio_network_adaptor_config) {
     RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
@@ -1072,7 +1081,8 @@
       const std::map<int, webrtc::SdpAudioFormat>& decoder_map,
       absl::optional<webrtc::AudioCodecPairId> codec_pair_id,
       size_t jitter_buffer_max_packets,
-      bool jitter_buffer_fast_accelerate)
+      bool jitter_buffer_fast_accelerate,
+      rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor)
       : call_(call), config_() {
     RTC_DCHECK(call);
     config_.rtp.remote_ssrc = remote_ssrc;
@@ -1089,6 +1099,7 @@
     config_.decoder_factory = decoder_factory;
     config_.decoder_map = decoder_map;
     config_.codec_pair_id = codec_pair_id;
+    config_.frame_decryptor = frame_decryptor;
     RecreateAudioReceiveStream();
   }
 
@@ -1097,6 +1108,13 @@
     call_->DestroyAudioReceiveStream(stream_);
   }
 
+  void SetFrameDecryptor(
+      rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) {
+    RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+    config_.frame_decryptor = frame_decryptor;
+    RecreateAudioReceiveStream();
+  }
+
   void SetLocalSsrc(uint32_t local_ssrc) {
     RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
     config_.rtp.local_ssrc = local_ssrc;
@@ -1745,7 +1763,7 @@
   WebRtcAudioSendStream* stream = new WebRtcAudioSendStream(
       ssrc, mid_, sp.cname, sp.id, send_codec_spec_, send_rtp_extensions_,
       max_send_bitrate_bps_, audio_network_adaptor_config, call_, this,
-      engine()->encoder_factory_, codec_pair_id_);
+      engine()->encoder_factory_, codec_pair_id_, nullptr);
   send_streams_.insert(std::make_pair(ssrc, stream));
 
   // At this point the stream's local SSRC has been updated. If it is the first
@@ -1831,7 +1849,8 @@
                 recv_nack_enabled_, sp.stream_ids(), recv_rtp_extensions_,
                 call_, this, engine()->decoder_factory_, decoder_map_,
                 codec_pair_id_, engine()->audio_jitter_buffer_max_packets_,
-                engine()->audio_jitter_buffer_fast_accelerate_)));
+                engine()->audio_jitter_buffer_fast_accelerate_,
+                unsignaled_frame_decryptor_)));
   recv_streams_[ssrc]->SetPlayout(playout_);
 
   return true;
@@ -1912,6 +1931,30 @@
   return dtmf_payload_type_.has_value() && send_;
 }
 
+void WebRtcVoiceMediaChannel::SetFrameDecryptor(
+    uint32_t ssrc,
+    rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) {
+  RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+  auto matching_stream = recv_streams_.find(ssrc);
+  if (matching_stream != recv_streams_.end()) {
+    matching_stream->second->SetFrameDecryptor(frame_decryptor);
+  }
+  // Handle unsignaled frame decryptors.
+  if (ssrc == 0) {
+    unsignaled_frame_decryptor_ = frame_decryptor;
+  }
+}
+
+void WebRtcVoiceMediaChannel::SetFrameEncryptor(
+    uint32_t ssrc,
+    rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor) {
+  RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+  auto matching_stream = send_streams_.find(ssrc);
+  if (matching_stream != send_streams_.end()) {
+    matching_stream->second->SetFrameEncryptor(frame_encryptor);
+  }
+}
+
 bool WebRtcVoiceMediaChannel::InsertDtmf(uint32_t ssrc,
                                          int event,
                                          int duration) {
diff --git a/media/engine/webrtcvoiceengine.h b/media/engine/webrtcvoiceengine.h
index db3f7c2..bedede1 100644
--- a/media/engine/webrtcvoiceengine.h
+++ b/media/engine/webrtcvoiceengine.h
@@ -170,6 +170,21 @@
   bool RemoveSendStream(uint32_t ssrc) override;
   bool AddRecvStream(const StreamParams& sp) override;
   bool RemoveRecvStream(uint32_t ssrc) override;
+
+  // E2EE Frame API
+  // Set a frame decryptor to a particular ssrc that will intercept all
+  // incoming audio payloads and attempt to decrypt them before forwarding the
+  // result.
+  void SetFrameDecryptor(uint32_t ssrc,
+                         rtc::scoped_refptr<webrtc::FrameDecryptorInterface>
+                             frame_decryptor) override;
+  // Set a frame encryptor to a particular ssrc that will intercept all
+  // outgoing audio payloads frames and attempt to encrypt them and forward the
+  // result to the packetizer.
+  void SetFrameEncryptor(uint32_t ssrc,
+                         rtc::scoped_refptr<webrtc::FrameEncryptorInterface>
+                             frame_encryptor) override;
+
   // SSRC=0 will apply the new volume to current and future unsignaled streams.
   bool SetOutputVolume(uint32_t ssrc, double volume) override;
 
@@ -287,6 +302,10 @@
   const webrtc::AudioCodecPairId codec_pair_id_ =
       webrtc::AudioCodecPairId::Create();
 
+  // Unsignaled streams have an option to have a frame decryptor set on them.
+  rtc::scoped_refptr<webrtc::FrameDecryptorInterface>
+      unsignaled_frame_decryptor_;
+
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(WebRtcVoiceMediaChannel);
 };
 }  // namespace cricket
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 3ad3340..4e6e558 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -492,6 +492,7 @@
     deps = [
       ":peerconnection",
       ":rtc_pc_base",
+      "../api:fake_frame_crypto",
       "../api:libjingle_peerconnection_api",
       "../api:mock_rtp",
       "../api/units:time_delta",
diff --git a/pc/rtpreceiver.cc b/pc/rtpreceiver.cc
index 8ab08bf..d4a1f62 100644
--- a/pc/rtpreceiver.cc
+++ b/pc/rtpreceiver.cc
@@ -43,13 +43,17 @@
   return streams;
 }
 
-void AttachFrameDecryptorToMediaChannel(
+// Attempt to attach the frame decryptor to the current media channel on the
+// correct worker thread only if both the media channel exists and a ssrc has
+// been allocated to the stream.
+void MaybeAttachFrameDecryptorToMediaChannel(
+    const absl::optional<uint32_t>& ssrc,
     rtc::Thread* worker_thread,
-    webrtc::FrameDecryptorInterface* frame_decryptor,
+    rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor,
     cricket::MediaChannel* media_channel) {
-  if (media_channel) {
+  if (media_channel && ssrc.has_value()) {
     return worker_thread->Invoke<void>(RTC_FROM_HERE, [&] {
-      media_channel->SetFrameDecryptor(frame_decryptor);
+      media_channel->SetFrameDecryptor(*ssrc, frame_decryptor);
     });
   }
 }
@@ -152,8 +156,9 @@
 void AudioRtpReceiver::SetFrameDecryptor(
     rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor) {
   frame_decryptor_ = std::move(frame_decryptor);
-  AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(),
-                                     media_channel_);
+  // Attach the frame decryptor to the media channel if it exists.
+  MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_,
+                                          frame_decryptor_, media_channel_);
 }
 
 rtc::scoped_refptr<FrameDecryptorInterface>
@@ -246,6 +251,9 @@
   if (!SetOutputVolume(track_->enabled() ? cached_volume_ : 0)) {
     RTC_NOTREACHED();
   }
+  // Reattach the frame decryptor if we were reconfigured.
+  MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_,
+                                          frame_decryptor_, media_channel_);
 }
 
 void AudioRtpReceiver::SetObserver(RtpReceiverObserverInterface* observer) {
@@ -259,8 +267,6 @@
 void AudioRtpReceiver::SetVoiceMediaChannel(
     cricket::VoiceMediaChannel* voice_media_channel) {
   media_channel_ = voice_media_channel;
-  AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(),
-                                     media_channel_);
 }
 
 void AudioRtpReceiver::NotifyFirstPacketReceived() {
@@ -341,8 +347,9 @@
 void VideoRtpReceiver::SetFrameDecryptor(
     rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor) {
   frame_decryptor_ = std::move(frame_decryptor);
-  AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(),
-                                     media_channel_);
+  // Attach the new frame decryptor the media channel if it exists yet.
+  MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_,
+                                          frame_decryptor_, media_channel_);
 }
 
 rtc::scoped_refptr<FrameDecryptorInterface>
@@ -379,6 +386,9 @@
   }
   ssrc_ = ssrc;
   SetSink(source_->sink());
+  // Attach any existing frame decryptor to the media channel.
+  MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_,
+                                          frame_decryptor_, media_channel_);
 }
 
 void VideoRtpReceiver::set_stream_ids(std::vector<std::string> stream_ids) {
@@ -429,8 +439,6 @@
 void VideoRtpReceiver::SetVideoMediaChannel(
     cricket::VideoMediaChannel* video_media_channel) {
   media_channel_ = video_media_channel;
-  AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(),
-                                     media_channel_);
 }
 
 void VideoRtpReceiver::NotifyFirstPacketReceived() {
diff --git a/pc/rtpsender.cc b/pc/rtpsender.cc
index 20130a1..3bb90f2 100644
--- a/pc/rtpsender.cc
+++ b/pc/rtpsender.cc
@@ -31,20 +31,6 @@
   return ++g_unique_id;
 }
 
-// Attaches the frame encryptor to the media channel through an invoke on a
-// worker thread. This set must be done on the corresponding worker thread that
-// the media channel was created on.
-void AttachFrameEncryptorToMediaChannel(
-    rtc::Thread* worker_thread,
-    webrtc::FrameEncryptorInterface* frame_encryptor,
-    cricket::MediaChannel* media_channel) {
-  if (media_channel) {
-    return worker_thread->Invoke<void>(RTC_FROM_HERE, [&] {
-      media_channel->SetFrameEncryptor(frame_encryptor);
-    });
-  }
-}
-
 // Returns an true if any RtpEncodingParameters member that isn't implemented
 // contains a value.
 bool UnimplementedRtpEncodingParameterHasValue(
@@ -78,6 +64,21 @@
   return false;
 }
 
+// Attempt to attach the frame decryptor to the current media channel on the
+// correct worker thread only if both the media channel exists and a ssrc has
+// been allocated to the stream.
+void MaybeAttachFrameEncryptorToMediaChannel(
+    const absl::optional<uint32_t> ssrc,
+    rtc::Thread* worker_thread,
+    rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor,
+    cricket::MediaChannel* media_channel) {
+  if (media_channel && ssrc.has_value()) {
+    return worker_thread->Invoke<void>(RTC_FROM_HERE, [&] {
+      media_channel->SetFrameEncryptor(*ssrc, frame_encryptor);
+    });
+  }
+}
+
 }  // namespace
 
 // Returns true if any RtpParameters member that isn't implemented contains a
@@ -304,8 +305,8 @@
 void AudioRtpSender::SetFrameEncryptor(
     rtc::scoped_refptr<FrameEncryptorInterface> frame_encryptor) {
   frame_encryptor_ = std::move(frame_encryptor);
-  AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(),
-                                     media_channel_);
+  MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_,
+                                          frame_encryptor_, media_channel_);
 }
 
 rtc::scoped_refptr<FrameEncryptorInterface> AudioRtpSender::GetFrameEncryptor()
@@ -354,6 +355,9 @@
       init_parameters_.encodings.clear();
     });
   }
+  // Each time there is an ssrc update.
+  MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_,
+                                          frame_encryptor_, media_channel_);
 }
 
 void AudioRtpSender::Stop() {
@@ -379,8 +383,6 @@
 void AudioRtpSender::SetVoiceMediaChannel(
     cricket::VoiceMediaChannel* voice_media_channel) {
   media_channel_ = voice_media_channel;
-  AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(),
-                                     media_channel_);
 }
 
 void AudioRtpSender::SetAudioSend() {
@@ -555,8 +557,8 @@
 void VideoRtpSender::SetFrameEncryptor(
     rtc::scoped_refptr<FrameEncryptorInterface> frame_encryptor) {
   frame_encryptor_ = std::move(frame_encryptor);
-  AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(),
-                                     media_channel_);
+  MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_,
+                                          frame_encryptor_, media_channel_);
 }
 
 rtc::scoped_refptr<FrameEncryptorInterface> VideoRtpSender::GetFrameEncryptor()
@@ -599,6 +601,8 @@
       init_parameters_.encodings.clear();
     });
   }
+  MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_,
+                                          frame_encryptor_, media_channel_);
 }
 
 void VideoRtpSender::Stop() {
@@ -620,8 +624,6 @@
 void VideoRtpSender::SetVideoMediaChannel(
     cricket::VideoMediaChannel* video_media_channel) {
   media_channel_ = video_media_channel;
-  AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(),
-                                     media_channel_);
 }
 
 void VideoRtpSender::SetVideoSend() {
diff --git a/pc/rtpsenderreceiver_unittest.cc b/pc/rtpsenderreceiver_unittest.cc
index 2af2ebf..07ff6a3 100644
--- a/pc/rtpsenderreceiver_unittest.cc
+++ b/pc/rtpsenderreceiver_unittest.cc
@@ -13,6 +13,8 @@
 #include <utility>
 
 #include "api/rtpparameters.h"
+#include "api/test/fake_frame_decryptor.h"
+#include "api/test/fake_frame_encryptor.h"
 #include "media/base/fakemediaengine.h"
 #include "media/base/rtpdataengine.h"
 #include "media/base/testutils.h"
@@ -1411,4 +1413,26 @@
   EXPECT_TRUE(audio_sender_destroyed_signal_fired_);
 }
 
+// Validate that the default FrameEncryptor setting is nullptr.
+TEST_F(RtpSenderReceiverTest, AudioSenderCanSetFrameEncryptor) {
+  CreateAudioRtpSender();
+  rtc::scoped_refptr<FrameEncryptorInterface> fake_frame_encryptor(
+      new FakeFrameEncryptor());
+  EXPECT_EQ(nullptr, audio_rtp_sender_->GetFrameEncryptor());
+  audio_rtp_sender_->SetFrameEncryptor(fake_frame_encryptor);
+  EXPECT_EQ(fake_frame_encryptor.get(),
+            audio_rtp_sender_->GetFrameEncryptor().get());
+}
+
+// Validate that the default FrameEncryptor setting is nullptr.
+TEST_F(RtpSenderReceiverTest, AudioReceiverCanSetFrameDecryptor) {
+  CreateAudioRtpReceiver();
+  rtc::scoped_refptr<FrameDecryptorInterface> fake_frame_decryptor(
+      new FakeFrameDecryptor());
+  EXPECT_EQ(nullptr, audio_rtp_receiver_->GetFrameDecryptor());
+  audio_rtp_receiver_->SetFrameDecryptor(fake_frame_decryptor);
+  EXPECT_EQ(fake_frame_decryptor.get(),
+            audio_rtp_receiver_->GetFrameDecryptor().get());
+}
+
 }  // namespace webrtc