diff --git a/webrtc/api/BUILD.gn b/webrtc/api/BUILD.gn index ecaae998d2..6de99f7da0 100644 --- a/webrtc/api/BUILD.gn +++ b/webrtc/api/BUILD.gn @@ -24,6 +24,7 @@ rtc_source_set("call_api") { "call/audio_send_stream.h", "call/audio_sink.h", "call/audio_state.h", + "call/flexfec_receive_stream.h", ] deps = [ diff --git a/webrtc/api/api.gyp b/webrtc/api/api.gyp index d017cda2d6..c431e9e669 100644 --- a/webrtc/api/api.gyp +++ b/webrtc/api/api.gyp @@ -108,6 +108,7 @@ 'call/audio_send_stream.h', 'call/audio_sink.h', 'call/audio_state.h', + 'call/flexfec_receive_stream.h' ], }, { diff --git a/webrtc/api/call/flexfec_receive_stream.h b/webrtc/api/call/flexfec_receive_stream.h new file mode 100644 index 0000000000..5918f7730f --- /dev/null +++ b/webrtc/api/call/flexfec_receive_stream.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 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 WEBRTC_API_CALL_FLEXFEC_RECEIVE_STREAM_H_ +#define WEBRTC_API_CALL_FLEXFEC_RECEIVE_STREAM_H_ + +#include + +#include "webrtc/config.h" + +namespace webrtc { + +// WORK IN PROGRESS! +// This class is under development and it is not yet intended for use outside +// of WebRTC. +// +// TODO(brandtr): Remove this comment when FlexFEC is ready for public use. + +class FlexfecReceiveStream { + public: + struct Stats { + std::string ToString(int64_t time_ms) const; + + // TODO(brandtr): Add appropriate stats here. + int flexfec_bitrate_bps; + }; + + // TODO(brandtr): When we add multistream protection, and thus add a + // FlexfecSendStream class, remove FlexfecConfig from config.h and add + // the appropriate configs here and in FlexfecSendStream. + using Config = FlexfecConfig; + + // Starts stream activity. + // When a stream is active, it can receive and process packets. + virtual void Start() = 0; + // Stops stream activity. + // When a stream is stopped, it can't receive nor process packets. + virtual void Stop() = 0; + + virtual Stats GetStats() const = 0; + + protected: + virtual ~FlexfecReceiveStream() = default; +}; + +} // namespace webrtc + +#endif // WEBRTC_API_CALL_FLEXFEC_RECEIVE_STREAM_H_ diff --git a/webrtc/call/BUILD.gn b/webrtc/call/BUILD.gn index 97643d1238..8ea5d0512e 100644 --- a/webrtc/call/BUILD.gn +++ b/webrtc/call/BUILD.gn @@ -12,6 +12,8 @@ rtc_static_library("call") { sources = [ "bitrate_allocator.cc", "call.cc", + "flexfec_receive_stream.cc", + "flexfec_receive_stream.h", "transport_adapter.cc", "transport_adapter.h", ] @@ -41,6 +43,7 @@ if (rtc_include_tests) { "bitrate_allocator_unittest.cc", "bitrate_estimator_tests.cc", "call_unittest.cc", + "flexfec_receive_stream_unittest.cc", "packet_injection_tests.cc", ] deps = [ diff --git a/webrtc/call/flexfec_receive_stream.cc b/webrtc/call/flexfec_receive_stream.cc new file mode 100644 index 0000000000..e3b3345f36 --- /dev/null +++ b/webrtc/call/flexfec_receive_stream.cc @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016 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 "webrtc/call/flexfec_receive_stream.h" + +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" + +namespace webrtc { + +std::string FlexfecReceiveStream::Stats::ToString(int64_t time_ms) const { + std::stringstream ss; + ss << "FlexfecReceiveStream stats: " << time_ms + << ", {flexfec_bitrate_bps: " << flexfec_bitrate_bps << "}"; + return ss.str(); +} + +namespace { + +// TODO(brandtr): Update this function when we support multistream protection. +std::unique_ptr MaybeUpdateConfigAndCreateFlexfecReceiver( + FlexfecReceiveStream::Config* config, + RecoveredPacketReceiver* recovered_packet_callback) { + if (config->protected_media_ssrcs.empty()) { + LOG(LS_ERROR) << "No protected media SSRC supplied. " + << "This FlexfecReceiveStream will therefore be useless."; + return nullptr; + } else if (config->protected_media_ssrcs.size() > 1) { + LOG(LS_WARNING) + << "The supplied FlexfecConfig contained multiple protected " + "media streams, but our implementation currently only " + "supports protecting a single media stream. This " + "FlexfecReceiveStream will therefore only accept media " + "packets from the first supplied media stream, with SSRC " + << config->protected_media_ssrcs[0] << "."; + config->protected_media_ssrcs.resize(1); + } + return FlexfecReceiver::Create(config->flexfec_ssrc, + config->protected_media_ssrcs[0], + recovered_packet_callback); +} + +} // namespace + +namespace internal { + +FlexfecReceiveStream::FlexfecReceiveStream( + Config configuration, + RecoveredPacketReceiver* recovered_packet_callback) + : started_(false), + config_(configuration), + receiver_(MaybeUpdateConfigAndCreateFlexfecReceiver( + &config_, + recovered_packet_callback)) { + LOG(LS_INFO) << "FlexfecReceiveStream: " << config_.ToString(); +} + +FlexfecReceiveStream::~FlexfecReceiveStream() { + LOG(LS_INFO) << "~FlexfecReceiveStream: " << config_.ToString(); + Stop(); +} + +bool FlexfecReceiveStream::AddAndProcessReceivedPacket(const uint8_t* packet, + size_t packet_length) { + { + rtc::CritScope cs(&crit_); + if (!started_) + return false; + } + if (!receiver_) + return false; + return receiver_->AddAndProcessReceivedPacket(packet, packet_length); +} + +void FlexfecReceiveStream::Start() { + rtc::CritScope cs(&crit_); + started_ = true; +} + +void FlexfecReceiveStream::Stop() { + rtc::CritScope cs(&crit_); + started_ = false; +} + +// TODO(brandtr): Implement this member function when we have designed the +// stats for FlexFEC. +FlexfecReceiveStream::Stats FlexfecReceiveStream::GetStats() const { + return webrtc::FlexfecReceiveStream::Stats(); +} + +} // namespace internal + +} // namespace webrtc diff --git a/webrtc/call/flexfec_receive_stream.h b/webrtc/call/flexfec_receive_stream.h new file mode 100644 index 0000000000..19bb9f873b --- /dev/null +++ b/webrtc/call/flexfec_receive_stream.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016 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 WEBRTC_CALL_FLEXFEC_RECEIVE_STREAM_H_ +#define WEBRTC_CALL_FLEXFEC_RECEIVE_STREAM_H_ + +#include +#include + +#include "webrtc/api/call/flexfec_receive_stream.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/modules/rtp_rtcp/include/flexfec_receiver.h" + +namespace webrtc { + +namespace internal { + +class FlexfecReceiveStream : public webrtc::FlexfecReceiveStream { + public: + FlexfecReceiveStream(Config configuration, + RecoveredPacketReceiver* recovered_packet_callback); + ~FlexfecReceiveStream(); + + const Config& config() const { return config_; } + + bool AddAndProcessReceivedPacket(const uint8_t* packet, size_t length); + + // Implements webrtc::FlexfecReceiveStream. + void Start() override; + void Stop() override; + Stats GetStats() const override; + + private: + rtc::CriticalSection crit_; + bool started_ GUARDED_BY(crit_); + + Config config_; + const std::unique_ptr receiver_; +}; + +} // namespace internal + +} // namespace webrtc + +#endif // WEBRTC_CALL_FLEXFEC_RECEIVE_STREAM_H_ diff --git a/webrtc/call/flexfec_receive_stream_unittest.cc b/webrtc/call/flexfec_receive_stream_unittest.cc new file mode 100644 index 0000000000..a7a3158ee5 --- /dev/null +++ b/webrtc/call/flexfec_receive_stream_unittest.cc @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2016 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 "webrtc/base/basictypes.h" +#include "webrtc/call/flexfec_receive_stream.h" +#include "webrtc/modules/rtp_rtcp/include/flexfec_receiver.h" +#include "webrtc/modules/rtp_rtcp/source/byte_io.h" +#include "webrtc/modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h" +#include "webrtc/test/gmock.h" +#include "webrtc/test/gtest.h" + +namespace webrtc { + +TEST(FlexfecReceiveStreamTest, ConstructDestruct) { + FlexfecReceiveStream::Config config; + config.flexfec_payload_type = 118; + config.flexfec_ssrc = 424223; + config.protected_media_ssrcs = {912512}; + MockRecoveredPacketReceiver callback; + + internal::FlexfecReceiveStream receive_stream(config, &callback); +} + +TEST(FlexfecReceiveStreamTest, StartStop) { + FlexfecReceiveStream::Config config; + config.flexfec_payload_type = 118; + config.flexfec_ssrc = 1652392; + config.protected_media_ssrcs = {23300443}; + MockRecoveredPacketReceiver callback; + internal::FlexfecReceiveStream receive_stream(config, &callback); + + receive_stream.Start(); + receive_stream.Stop(); +} + +TEST(FlexfecReceiveStreamTest, DoesNotProcessPacketWhenNoMediaSsrcGiven) { + FlexfecReceiveStream::Config config; + config.flexfec_payload_type = 118; + config.flexfec_ssrc = 424223; + config.protected_media_ssrcs = {}; + MockRecoveredPacketReceiver callback; + internal::FlexfecReceiveStream receive_stream(config, &callback); + const uint8_t packet[] = {0x00, 0x11, 0x22, 0x33}; + const size_t packet_length = sizeof(packet); + + EXPECT_FALSE( + receive_stream.AddAndProcessReceivedPacket(packet, packet_length)); +} + +// TODO(brandtr): Remove when we support multistream protection. +TEST(FlexfecReceiveStreamTest, CannotProtectMultipleMediaStreams) { + FlexfecReceiveStream::Config config; + config.flexfec_payload_type = 118; + config.flexfec_ssrc = 424223; + config.protected_media_ssrcs = {123, 456}; + MockRecoveredPacketReceiver callback; + internal::FlexfecReceiveStream receive_stream(config, &callback); + + ASSERT_EQ(1U, receive_stream.config().protected_media_ssrcs.size()); + EXPECT_EQ(config.protected_media_ssrcs[0], + receive_stream.config().protected_media_ssrcs[0]); +} + +// Create a FlexFEC packet that protects a single media packet and ensure +// that the callback is called. Correctness of recovery is checked in the +// FlexfecReceiver unit tests. +TEST(FlexfecReceiveStreamTest, RecoversPacketWhenStarted) { + constexpr uint8_t kFlexfecPlType = 118; + constexpr uint8_t kFlexfecSeqNum[] = {0x00, 0x01}; + constexpr uint8_t kFlexfecTs[] = {0x00, 0x11, 0x22, 0x33}; + constexpr uint8_t kFlexfecSsrc[] = {0x00, 0x00, 0x00, 0x01}; + constexpr uint8_t kMediaPlType = 107; + constexpr uint8_t kMediaSeqNum[] = {0x00, 0x02}; + constexpr uint8_t kMediaTs[] = {0xaa, 0xbb, 0xcc, 0xdd}; + constexpr uint8_t kMediaSsrc[] = {0x00, 0x00, 0x00, 0x02}; + + // This packet mask protects a single media packet, i.e., the FlexFEC payload + // is a copy of that media packet. When inserted in the FlexFEC pipeline, + // it will thus trivially recover the lost media packet. + constexpr uint8_t kKBit0 = 1 << 7; + constexpr uint8_t kFlexfecPktMask[] = {kKBit0 | 0x00, 0x01}; + constexpr uint8_t kPayloadLength[] = {0x00, 0x04}; + constexpr uint8_t kSsrcCount = 1; + constexpr uint8_t kReservedBits = 0x00; + constexpr uint8_t kPayloadBits = 0x00; + // clang-format off + constexpr uint8_t kFlexfecPacket[] = { + // RTP header. + 0x80, kFlexfecPlType, kFlexfecSeqNum[0], kFlexfecSeqNum[1], + kFlexfecTs[0], kFlexfecTs[1], kFlexfecTs[2], kFlexfecTs[3], + kFlexfecSsrc[0], kFlexfecSsrc[1], kFlexfecSsrc[2], kFlexfecSsrc[3], + // FlexFEC header. + 0x00, kMediaPlType, kPayloadLength[0], kPayloadLength[1], + kMediaTs[0], kMediaTs[1], kMediaTs[2], kMediaTs[3], + kSsrcCount, kReservedBits, kReservedBits, kReservedBits, + kMediaSsrc[0], kMediaSsrc[1], kMediaSsrc[2], kMediaSsrc[3], + kMediaSeqNum[0], kMediaSeqNum[1], kFlexfecPktMask[0], kFlexfecPktMask[1], + // FEC payload. + kPayloadBits, kPayloadBits, kPayloadBits, kPayloadBits}; + // clang-format on + constexpr size_t kFlexfecPacketLength = sizeof(kFlexfecPacket); + + FlexfecReceiveStream::Config config; + config.flexfec_payload_type = kFlexfecPlType; + config.flexfec_ssrc = ByteReader::ReadBigEndian(kFlexfecSsrc); + config.protected_media_ssrcs = { + ByteReader::ReadBigEndian(kMediaSsrc)}; + testing::StrictMock recovered_packet_receiver; + internal::FlexfecReceiveStream receive_stream(config, + &recovered_packet_receiver); + + // Do not call back before being started. + receive_stream.AddAndProcessReceivedPacket(kFlexfecPacket, + kFlexfecPacketLength); + + // Call back after being started. + receive_stream.Start(); + EXPECT_CALL( + recovered_packet_receiver, + OnRecoveredPacket(::testing::_, kRtpHeaderSize + kPayloadLength[1])); + receive_stream.AddAndProcessReceivedPacket(kFlexfecPacket, + kFlexfecPacketLength); +} + +} // namespace webrtc diff --git a/webrtc/call/webrtc_call.gypi b/webrtc/call/webrtc_call.gypi index 59dcef6af6..fd1d9c5646 100644 --- a/webrtc/call/webrtc_call.gypi +++ b/webrtc/call/webrtc_call.gypi @@ -17,6 +17,8 @@ 'webrtc_call_sources': [ 'call/bitrate_allocator.cc', 'call/call.cc', + 'call/flexfec_receive_stream.cc', + 'call/flexfec_receive_stream.h', 'call/transport_adapter.cc', 'call/transport_adapter.h', ], diff --git a/webrtc/config.cc b/webrtc/config.cc index 0331c0a833..04f6a66935 100644 --- a/webrtc/config.cc +++ b/webrtc/config.cc @@ -31,6 +31,20 @@ std::string UlpfecConfig::ToString() const { return ss.str(); } +std::string FlexfecConfig::ToString() const { + std::stringstream ss; + ss << "{flexfec_payload_type: " << flexfec_payload_type; + ss << ", flexfec_ssrc: " << flexfec_ssrc; + ss << ", protected_media_ssrcs: ["; + size_t i = 0; + for (; i + 1 < protected_media_ssrcs.size(); ++i) + ss << protected_media_ssrcs[i] << ", "; + if (!protected_media_ssrcs.empty()) + ss << protected_media_ssrcs[i]; + ss << "]}"; + return ss.str(); +} + std::string RtpExtension::ToString() const { std::stringstream ss; ss << "{uri: " << uri; diff --git a/webrtc/config.h b/webrtc/config.h index a6778715d8..4ec895b0dd 100644 --- a/webrtc/config.h +++ b/webrtc/config.h @@ -16,6 +16,7 @@ #include #include +#include "webrtc/base/basictypes.h" #include "webrtc/base/optional.h" #include "webrtc/base/refcount.h" #include "webrtc/base/scoped_ref_ptr.h" @@ -53,6 +54,26 @@ struct UlpfecConfig { int red_rtx_payload_type; }; +// Settings for FlexFEC forward error correction. +// Set the payload type to '-1' to disable. +struct FlexfecConfig { + FlexfecConfig() + : flexfec_payload_type(-1), flexfec_ssrc(0), protected_media_ssrcs() {} + std::string ToString() const; + + // Payload type of FlexFEC. + int flexfec_payload_type; + + // SSRC of FlexFEC stream. + uint32_t flexfec_ssrc; + + // Vector containing a single element, corresponding to the SSRC of the media + // stream being protected by this FlexFEC stream. The vector MUST have size 1. + // + // TODO(brandtr): Update comment above when we support multistream protection. + std::vector protected_media_ssrcs; +}; + // RTP header extension, see RFC 5285. struct RtpExtension { RtpExtension() : id(0) {}