From 95eb1ba0db79d8fd134ae61b0a24648598684e8a Mon Sep 17 00:00:00 2001 From: ossu Date: Wed, 13 Jul 2016 06:05:27 -0700 Subject: [PATCH] WebRtcVoiceEngine: Use AudioDecoderFactory to generate recv codecs. Changed WebRtcVoiceEngine to present receive codecs from the formats provided by its decoder factory. Added supported formats to BuiltinAudioDecoderFactory. Added helper functions for creating some simple decoder factories for mocking. Created a PayloadTypeMapper for assigning payload types to formats. I think we'll eventually want to use this further up, or possibly in both the audio and video sides. It would be best if the engines didn't have to talk payload types at all, but it might be more difficult to get around when payload types depend on each-other, like the RTX codecs for video. This CL also includes some changes to rtc::Optional. I've put them in a separate CL that should (or should not) land first, making these changes void. See: https://codereview.webrtc.org/2072713002/ BUG=webrtc:5805 Review-Url: https://codereview.webrtc.org/2072753002 Cr-Commit-Position: refs/heads/master@{#13459} --- webrtc/media/BUILD.gn | 3 + .../engine/nullwebrtcvideoengine_unittest.cc | 5 +- webrtc/media/engine/payload_type_mapper.cc | 159 +++++++++++++++++ webrtc/media/engine/payload_type_mapper.h | 54 ++++++ .../engine/payload_type_mapper_unittest.cc | 162 ++++++++++++++++++ webrtc/media/engine/webrtcmediaengine.h | 1 + webrtc/media/engine/webrtcvoiceengine.cc | 76 +++++++- webrtc/media/engine/webrtcvoiceengine.h | 5 +- .../engine/webrtcvoiceengine_unittest.cc | 38 ++-- webrtc/media/media.gyp | 3 + .../codecs/builtin_audio_decoder_factory.cc | 26 ++- .../codecs/mock/mock_audio_decoder_factory.h | 40 +++++ 12 files changed, 542 insertions(+), 30 deletions(-) create mode 100644 webrtc/media/engine/payload_type_mapper.cc create mode 100644 webrtc/media/engine/payload_type_mapper.h create mode 100644 webrtc/media/engine/payload_type_mapper_unittest.cc diff --git a/webrtc/media/BUILD.gn b/webrtc/media/BUILD.gn index 34e815501c..b4511fbc59 100644 --- a/webrtc/media/BUILD.gn +++ b/webrtc/media/BUILD.gn @@ -92,6 +92,8 @@ source_set("rtc_media") { "base/videosourcebase.h", "devices/videorendererfactory.h", "engine/nullwebrtcvideoengine.h", + "engine/payload_type_mapper.cc", + "engine/payload_type_mapper.h", "engine/simulcast.cc", "engine/simulcast.h", "engine/webrtccommon.h", @@ -301,6 +303,7 @@ if (rtc_include_tests) { "base/videoengine_unittest.h", "base/videoframe_unittest.h", "engine/nullwebrtcvideoengine_unittest.cc", + "engine/payload_type_mapper_unittest.cc", "engine/simulcast_unittest.cc", "engine/webrtcmediaengine_unittest.cc", "engine/webrtcvideocapturer_unittest.cc", diff --git a/webrtc/media/engine/nullwebrtcvideoengine_unittest.cc b/webrtc/media/engine/nullwebrtcvideoengine_unittest.cc index 38d6e77488..52cd0f2a62 100644 --- a/webrtc/media/engine/nullwebrtcvideoengine_unittest.cc +++ b/webrtc/media/engine/nullwebrtcvideoengine_unittest.cc @@ -12,6 +12,7 @@ #include "webrtc/media/engine/nullwebrtcvideoengine.h" #include "webrtc/media/engine/webrtcvoiceengine.h" +#include "webrtc/modules/audio_coding/codecs/mock/mock_audio_decoder_factory.h" namespace cricket { @@ -34,7 +35,9 @@ class WebRtcMediaEngineNullVideo // Simple test to check if NullWebRtcVideoEngine implements the methods // required by CompositeMediaEngine. TEST(NullWebRtcVideoEngineTest, CheckInterface) { - WebRtcMediaEngineNullVideo engine(nullptr, nullptr, nullptr, nullptr); + WebRtcMediaEngineNullVideo engine( + nullptr, webrtc::MockAudioDecoderFactory::CreateUnusedFactory(), nullptr, + nullptr); EXPECT_TRUE(engine.Init()); } diff --git a/webrtc/media/engine/payload_type_mapper.cc b/webrtc/media/engine/payload_type_mapper.cc new file mode 100644 index 0000000000..87b54be33b --- /dev/null +++ b/webrtc/media/engine/payload_type_mapper.cc @@ -0,0 +1,159 @@ +/* + * 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/media/engine/payload_type_mapper.h" + +#include "webrtc/common_types.h" +#include "webrtc/media/base/mediaconstants.h" + +namespace cricket { + +PayloadTypeMapper::PayloadTypeMapper() + // RFC 3551 reserves payload type numbers in the range 96-127 exclusively + // for dynamic assignment. Once those are used up, it is recommended that + // payload types unassigned by the RFC are used for dynamic payload type + // mapping, before any static payload ids. At this point, we only support + // mapping within the exclusive range. + : next_unused_payload_type_(96), + max_payload_type_(127), + mappings_({ + // Static payload type assignments according to RFC 3551. + {{"PCMU", 8000, 1}, 0}, + {{"GSM", 8000, 1}, 3}, + {{"G723", 8000, 1}, 4}, + {{"DVI4", 8000, 1}, 5}, + {{"DVI4", 16000, 1}, 6}, + {{"LPC", 8000, 1}, 7}, + {{"PCMA", 8000, 1}, 8}, + {{"G722", 8000, 1}, 9}, + {{"L16", 44100, 2}, 10}, + {{"L16", 44100, 1}, 11}, + {{"QCELP", 8000, 1}, 12}, + {{"CN", 8000, 1}, 13}, + // RFC 4566 is a bit ambiguous on the contents of the "encoding + // parameters" field, which, for audio, encodes the number of + // channels. It is "optional and may be omitted if the number of + // channels is one". Does that necessarily imply that an omitted + // encoding parameter means one channel? Since RFC 3551 doesn't + // specify a value for this parameter for MPA, I've included both 0 + // and 1 here, to increase the chances it will be correctly used if + // someone implements an MPEG audio encoder/decoder. + {{"MPA", 90000, 0}, 14}, + {{"MPA", 90000, 1}, 14}, + {{"G728", 8000, 1}, 15}, + {{"DVI4", 11025, 1}, 16}, + {{"DVI4", 22050, 1}, 17}, + {{"G729", 8000, 1}, 18}, + + // Payload type assignments currently used by WebRTC. + // Includes video, to reduce collisions (and thus reassignments) + // RTX codecs mapping to specific video payload types + {{kRtxCodecName, 90000, 0, + {{kCodecParamAssociatedPayloadType, + std::to_string(kDefaultVp8PlType)}}}, + kDefaultRtxVp8PlType}, + {{kRtxCodecName, 90000, 0, + {{kCodecParamAssociatedPayloadType, + std::to_string(kDefaultVp9PlType)}}}, + kDefaultRtxVp9PlType}, + {{kRtxCodecName, 90000, 0, + {{kCodecParamAssociatedPayloadType, + std::to_string(kDefaultRedPlType)}}}, + kDefaultRtxRedPlType}, + {{kRtxCodecName, 90000, 0, + {{kCodecParamAssociatedPayloadType, + std::to_string(kDefaultH264PlType)}}}, + kDefaultRtxH264PlType}, + // Other codecs + {{kVp8CodecName, 90000, 0}, kDefaultVp8PlType}, + {{kVp9CodecName, 90000, 0}, kDefaultVp9PlType}, + {{kIlbcCodecName, 8000, 1}, 102}, + {{kIsacCodecName, 16000, 1}, 103}, + {{kIsacCodecName, 32000, 1}, 104}, + {{kCnCodecName, 16000, 1}, 105}, + {{kCnCodecName, 32000, 1}, 106}, + {{kH264CodecName, 90000, 0}, kDefaultH264PlType}, + {{kOpusCodecName, 48000, 2, + {{"minptime", "10"}, {"useinbandfec", "1"}}}, 111}, + {{kRedCodecName, 90000, 0}, kDefaultRedPlType}, + {{kUlpfecCodecName, 90000, 0}, kDefaultUlpfecType}, + {{kDtmfCodecName, 8000, 1}, 126}}) { + // TODO(ossu): Try to keep this as change-proof as possible until we're able + // to remove the payload type constants from everywhere in the code. + for (const auto& mapping : mappings_) { + used_payload_types_.insert(mapping.second); + } +} + +PayloadTypeMapper::~PayloadTypeMapper() = default; + +rtc::Optional PayloadTypeMapper::GetMappingFor( + const webrtc::SdpAudioFormat& format) { + auto iter = mappings_.find(format); + if (iter != mappings_.end()) + return rtc::Optional(iter->second); + + for (; next_unused_payload_type_ <= max_payload_type_; + ++next_unused_payload_type_) { + int payload_type = next_unused_payload_type_; + if (used_payload_types_.find(payload_type) == used_payload_types_.end()) { + used_payload_types_.insert(payload_type); + mappings_[format] = payload_type; + ++next_unused_payload_type_; + return rtc::Optional(payload_type); + } + } + + return rtc::Optional(); +} + +rtc::Optional PayloadTypeMapper::FindMappingFor( + const webrtc::SdpAudioFormat& format) const { + auto iter = mappings_.find(format); + if (iter != mappings_.end()) + return rtc::Optional(iter->second); + + return rtc::Optional(); +} + +rtc::Optional PayloadTypeMapper::ToAudioCodec( + const webrtc::SdpAudioFormat& format) { + // TODO(ossu): We can safely set bitrate to zero here, since that field is + // not presented in the SDP. It is used to ferry around some target bitrate + // values for certain codecs (ISAC and Opus) and in ways it really + // shouldn't. It should be removed once we no longer use CodecInsts in the + // ACM or NetEq. + auto opt_payload_type = GetMappingFor(format); + if (opt_payload_type) { + AudioCodec codec(*opt_payload_type, format.name, format.clockrate_hz, 0, + format.num_channels); + codec.params = format.parameters; + return rtc::Optional(std::move(codec)); + } + + return rtc::Optional(); +} + +bool PayloadTypeMapper::SdpAudioFormatOrdering::operator()( + const webrtc::SdpAudioFormat& a, + const webrtc::SdpAudioFormat& b) const { + if (a.clockrate_hz == b.clockrate_hz) { + if (a.num_channels == b.num_channels) { + int name_cmp = STR_CASE_CMP(a.name.c_str(), b.name.c_str()); + if (name_cmp == 0) + return a.parameters < b.parameters; + return name_cmp < 0; + } + return a.num_channels < b.num_channels; + } + return a.clockrate_hz < b.clockrate_hz; +} + +} // namespace cricket diff --git a/webrtc/media/engine/payload_type_mapper.h b/webrtc/media/engine/payload_type_mapper.h new file mode 100644 index 0000000000..a79fb4756c --- /dev/null +++ b/webrtc/media/engine/payload_type_mapper.h @@ -0,0 +1,54 @@ +/* + * 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_MEDIA_ENGINE_PAYLOAD_TYPE_MAPPER_H_ +#define WEBRTC_MEDIA_ENGINE_PAYLOAD_TYPE_MAPPER_H_ + +#include +#include + +#include "webrtc/base/optional.h" +#include "webrtc/media/base/codec.h" +#include "webrtc/modules/audio_coding/codecs/audio_format.h" + +namespace cricket { + +class PayloadTypeMapper { + public: + PayloadTypeMapper(); + ~PayloadTypeMapper(); + + // Finds the current payload type for |format| or assigns a new one, if no + // current mapping exists. Will return an empty value if it was unable to + // create a mapping, i.e. if all dynamic payload type ids have been used up. + rtc::Optional GetMappingFor(const webrtc::SdpAudioFormat& format); + + // Finds the current payload type for |format|, if any. Returns an empty value + // if no payload type mapping exists for the format. + rtc::Optional FindMappingFor(const webrtc::SdpAudioFormat& format) const; + + // Like GetMappingFor, but fills in an AudioCodec structure with the necessary + // information instead. + rtc::Optional ToAudioCodec(const webrtc::SdpAudioFormat& format); + + private: + struct SdpAudioFormatOrdering { + bool operator()(const webrtc::SdpAudioFormat& a, + const webrtc::SdpAudioFormat& b) const; + }; + + int next_unused_payload_type_; + int max_payload_type_; + std::map mappings_; + std::set used_payload_types_; +}; + +} // namespace cricket +#endif // WEBRTC_MEDIA_ENGINE_PAYLOAD_TYPE_MAPPER_H_ diff --git a/webrtc/media/engine/payload_type_mapper_unittest.cc b/webrtc/media/engine/payload_type_mapper_unittest.cc new file mode 100644 index 0000000000..7042086638 --- /dev/null +++ b/webrtc/media/engine/payload_type_mapper_unittest.cc @@ -0,0 +1,162 @@ +/* + * 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 +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/media/engine/payload_type_mapper.h" + +namespace cricket { + +class PayloadTypeMapperTest : public testing::Test { + public: + // TODO(ossu): These are to work around missing comparison operators in + // rtc::Optional. They should be removed once Optional has been updated. + int FindMapping(const webrtc::SdpAudioFormat& format) { + auto opt_mapping = mapper_.FindMappingFor(format); + if (opt_mapping) + return *opt_mapping; + return -1; + } + + int GetMapping(const webrtc::SdpAudioFormat& format) { + auto opt_mapping = mapper_.GetMappingFor(format); + if (opt_mapping) + return *opt_mapping; + return -1; + } + + protected: + PayloadTypeMapper mapper_; +}; + +TEST_F(PayloadTypeMapperTest, StaticPayloadTypes) { + EXPECT_EQ(0, FindMapping({"pcmu", 8000, 1})); + EXPECT_EQ(3, FindMapping({"gsm", 8000, 1})); + EXPECT_EQ(4, FindMapping({"g723", 8000, 1})); + EXPECT_EQ(5, FindMapping({"dvi4", 8000, 1})); + EXPECT_EQ(6, FindMapping({"dvi4", 16000, 1})); + EXPECT_EQ(7, FindMapping({"lpc", 8000, 1})); + EXPECT_EQ(8, FindMapping({"pcma", 8000, 1})); + EXPECT_EQ(9, FindMapping({"g722", 8000, 1})); + EXPECT_EQ(10, FindMapping({"l16", 44100, 2})); + EXPECT_EQ(11, FindMapping({"l16", 44100, 1})); + EXPECT_EQ(12, FindMapping({"qcelp", 8000, 1})); + EXPECT_EQ(13, FindMapping({"cn", 8000, 1})); + EXPECT_EQ(14, FindMapping({"mpa", 90000, 0})); + EXPECT_EQ(14, FindMapping({"mpa", 90000, 1})); + EXPECT_EQ(15, FindMapping({"g728", 8000, 1})); + EXPECT_EQ(16, FindMapping({"dvi4", 11025, 1})); + EXPECT_EQ(17, FindMapping({"dvi4", 22050, 1})); + EXPECT_EQ(18, FindMapping({"g729", 8000, 1})); +} + +TEST_F(PayloadTypeMapperTest, WebRTCPayloadTypes) { + // Tests that the payload mapper knows about the formats we've been using in + // WebRTC, with their hard coded values. + auto video_mapping = [this] (const char *name) { + return FindMapping({name, kVideoCodecClockrate, 0}); + }; + EXPECT_EQ(kDefaultVp8PlType, video_mapping(kVp8CodecName)); + EXPECT_EQ(kDefaultVp9PlType, video_mapping(kVp9CodecName)); + EXPECT_EQ(kDefaultH264PlType, video_mapping(kH264CodecName)); + EXPECT_EQ(kDefaultRedPlType, video_mapping(kRedCodecName)); + EXPECT_EQ(kDefaultUlpfecType, video_mapping(kUlpfecCodecName)); + + auto rtx_mapping = [this] (int payload_type) { + return FindMapping({kRtxCodecName, kVideoCodecClockrate, 0, + {{ kCodecParamAssociatedPayloadType, std::to_string(payload_type)}}}); + }; + EXPECT_EQ(kDefaultRtxVp8PlType, rtx_mapping(kDefaultVp8PlType)); + EXPECT_EQ(kDefaultRtxVp9PlType, rtx_mapping(kDefaultVp9PlType)); + EXPECT_EQ(kDefaultRtxH264PlType, rtx_mapping(kDefaultH264PlType)); + EXPECT_EQ(kDefaultRtxRedPlType, rtx_mapping(kDefaultRedPlType)); + + EXPECT_EQ(102, FindMapping({kIlbcCodecName, 8000, 1})); + EXPECT_EQ(103, FindMapping({kIsacCodecName, 16000, 1})); + EXPECT_EQ(104, FindMapping({kIsacCodecName, 32000, 1})); + EXPECT_EQ(105, FindMapping({kCnCodecName, 16000, 1})); + EXPECT_EQ(106, FindMapping({kCnCodecName, 32000, 1})); + EXPECT_EQ(111, FindMapping({kOpusCodecName, 48000, 2, + {{"minptime", "10"}, {"useinbandfec", "1"}}})); + EXPECT_EQ(126, FindMapping({kDtmfCodecName, 8000, 1})); +} + +TEST_F(PayloadTypeMapperTest, ValidDynamicPayloadTypes) { + // RFC 3551 says: + // "This profile reserves payload type numbers in the range 96-127 + // exclusively for dynamic assignment. Applications SHOULD first use + // values in this range for dynamic payload types. Those applications + // which need to define more than 32 dynamic payload types MAY bind + // codes below 96, in which case it is RECOMMENDED that unassigned + // payload type numbers be used first. However, the statically assigned + // payload types are default bindings and MAY be dynamically bound to + // new encodings if needed." + + // Tests that the payload mapper uses values in the dynamic payload type range + // (96 - 127) before any others and that the values returned are all valid. + bool has_been_below_96 = false; + std::set used_payload_types; + for (int i = 0; i != 256; ++i) { + std::string format_name = "unknown_format_" + std::to_string(i); + webrtc::SdpAudioFormat format(format_name.c_str(), i*100, (i % 2) + 1); + auto opt_payload_type = mapper_.GetMappingFor(format); + bool mapper_is_full = false; + + // There's a limited number of slots for payload types. We're fine with not + // being able to map them all. + if (opt_payload_type) { + int payload_type = *opt_payload_type; + EXPECT_FALSE(mapper_is_full) << "Mapping should not fail sporadically"; + EXPECT_EQ(used_payload_types.find(payload_type), used_payload_types.end()) + << "Payload types must not be reused"; + used_payload_types.insert(payload_type); + EXPECT_GE(payload_type, 0) << "Negative payload types are invalid"; + EXPECT_LE(payload_type, 127) << "Payload types above 127 are invalid"; + EXPECT_FALSE(payload_type >= 96 && has_been_below_96); + if (payload_type < 96) + has_been_below_96 = true; + + EXPECT_EQ(payload_type, FindMapping(format)) + << "Mapping must be permanent after successful call to " + "GetMappingFor"; + EXPECT_EQ(payload_type, GetMapping(format)) + << "Subsequent calls to GetMappingFor must return the same value"; + } else { + mapper_is_full = true; + } + } + + // Also, we must've been able to map at least one dynamic payload type. + EXPECT_FALSE(used_payload_types.empty()) + << "Mapper must support at least one user-defined payload type"; +} + +TEST_F(PayloadTypeMapperTest, ToAudioCodec) { + webrtc::SdpAudioFormat format("unknown_format", 4711, 17); + auto opt_payload_type = mapper_.GetMappingFor(format); + EXPECT_TRUE(opt_payload_type); + auto opt_audio_codec = mapper_.ToAudioCodec(format); + EXPECT_TRUE(opt_audio_codec); + + if (opt_payload_type && opt_audio_codec) { + int payload_type = *opt_payload_type; + const AudioCodec& codec = *opt_audio_codec; + + EXPECT_EQ(codec.id, payload_type); + EXPECT_EQ(codec.name, format.name); + EXPECT_EQ(codec.clockrate, format.clockrate_hz); + EXPECT_EQ(codec.channels, format.num_channels); + EXPECT_EQ(codec.params, format.parameters); + } +} + +} // namespace cricket diff --git a/webrtc/media/engine/webrtcmediaengine.h b/webrtc/media/engine/webrtcmediaengine.h index 24deeb4d0e..dae63477d6 100644 --- a/webrtc/media/engine/webrtcmediaengine.h +++ b/webrtc/media/engine/webrtcmediaengine.h @@ -18,6 +18,7 @@ #include "webrtc/media/base/mediaengine.h" namespace webrtc { +class AudioDecoderFactory; class AudioDeviceModule; } namespace cricket { diff --git a/webrtc/media/engine/webrtcvoiceengine.cc b/webrtc/media/engine/webrtcvoiceengine.cc index 469098a322..59a2870cf8 100644 --- a/webrtc/media/engine/webrtcvoiceengine.cc +++ b/webrtc/media/engine/webrtcvoiceengine.cc @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -32,6 +33,7 @@ #include "webrtc/media/base/audiosource.h" #include "webrtc/media/base/mediaconstants.h" #include "webrtc/media/base/streamparams.h" +#include "webrtc/media/engine/payload_type_mapper.h" #include "webrtc/media/engine/webrtcmediaengine.h" #include "webrtc/media/engine/webrtcvoe.h" #include "webrtc/modules/audio_coding/acm2/rent_a_codec.h" @@ -248,7 +250,7 @@ class WebRtcVoiceCodecs final { public: // TODO(solenberg): Do this filtering once off-line, add a simple AudioCodec // list and add a test which verifies VoE supports the listed codecs. - static std::vector SupportedCodecs() { + static std::vector SupportedSendCodecs() { std::vector result; // Iterate first over our preferred codecs list, so that the results are // added in order of preference. @@ -511,13 +513,20 @@ WebRtcVoiceEngine::WebRtcVoiceEngine( RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); LOG(LS_INFO) << "WebRtcVoiceEngine::WebRtcVoiceEngine"; RTC_DCHECK(voe_wrapper); + RTC_DCHECK(decoder_factory); signal_thread_checker_.DetachFromThread(); // Load our audio codec list. - LOG(LS_INFO) << "Supported codecs in order of preference:"; - codecs_ = WebRtcVoiceCodecs::SupportedCodecs(); - for (const AudioCodec& codec : codecs_) { + LOG(LS_INFO) << "Supported send codecs in order of preference:"; + send_codecs_ = WebRtcVoiceCodecs::SupportedSendCodecs(); + for (const AudioCodec& codec : send_codecs_) { + LOG(LS_INFO) << ToString(codec); + } + + LOG(LS_INFO) << "Supported recv codecs in order of preference:"; + recv_codecs_ = CollectRecvCodecs(); + for (const AudioCodec& codec : recv_codecs_) { LOG(LS_INFO) << ToString(codec); } @@ -936,12 +945,12 @@ int WebRtcVoiceEngine::GetInputLevel() { const std::vector& WebRtcVoiceEngine::send_codecs() const { RTC_DCHECK(signal_thread_checker_.CalledOnValidThread()); - return codecs_; + return send_codecs_; } const std::vector& WebRtcVoiceEngine::recv_codecs() const { RTC_DCHECK(signal_thread_checker_.CalledOnValidThread()); - return codecs_; + return recv_codecs_; } RtpCapabilities WebRtcVoiceEngine::GetCapabilities() const { @@ -1081,6 +1090,61 @@ webrtc::AudioDeviceModule* WebRtcVoiceEngine::adm() { return adm_; } +AudioCodecs WebRtcVoiceEngine::CollectRecvCodecs() const { + PayloadTypeMapper mapper; + AudioCodecs out; + const std::vector& formats = + decoder_factory_->GetSupportedFormats(); + + // Only generate CN payload types for these clockrates + std::map> generate_cn = {{ 8000, false }, + { 16000, false }, + { 32000, false }}; + + auto map_format = [&mapper, &out] (const webrtc::SdpAudioFormat& format) { + rtc::Optional opt_codec = mapper.ToAudioCodec(format); + if (!opt_codec) { + LOG(LS_ERROR) << "Unable to assign payload type to format: " << format; + return false; + } + + auto& codec = *opt_codec; + if (IsCodec(codec, kOpusCodecName)) { + // TODO(ossu): Set this specifically for Opus for now, until we have a + // better way of dealing with rtcp-fb parameters. + codec.AddFeedbackParam( + FeedbackParam(kRtcpFbParamTransportCc, kParamValueEmpty)); + } + out.push_back(codec); + return true; + }; + + for (const auto& format : formats) { + if (map_format(format)) { + // TODO(ossu): We should get more than just a format from the factory, so + // we can determine if a format should be used with CN or not. For now, + // generate a CN entry for each supported clock rate also used by a format + // supported by the factory. + auto cn = generate_cn.find(format.clockrate_hz); + if (cn != generate_cn.end() /* && format.allow_comfort_noise */) { + cn->second = true; + } + } + } + + // Add CN codecs after "proper" audio codecs + for (const auto& cn : generate_cn) { + if (cn.second) { + map_format({kCnCodecName, cn.first, 1}); + } + } + + // Add telephone-event codec last + map_format({kDtmfCodecName, 8000, 1}); + + return out; +} + class WebRtcVoiceMediaChannel::WebRtcAudioSendStream : public AudioSource::Sink { public: diff --git a/webrtc/media/engine/webrtcvoiceengine.h b/webrtc/media/engine/webrtcvoiceengine.h index 64e0f5b185..7955c2090a 100644 --- a/webrtc/media/engine/webrtcvoiceengine.h +++ b/webrtc/media/engine/webrtcvoiceengine.h @@ -123,6 +123,8 @@ class WebRtcVoiceEngine final : public webrtc::TraceCallback { int CreateVoEChannel(); webrtc::AudioDeviceModule* adm(); + AudioCodecs CollectRecvCodecs() const; + rtc::ThreadChecker signal_thread_checker_; rtc::ThreadChecker worker_thread_checker_; @@ -132,7 +134,8 @@ class WebRtcVoiceEngine final : public webrtc::TraceCallback { // The primary instance of WebRtc VoiceEngine. std::unique_ptr voe_wrapper_; rtc::scoped_refptr audio_state_; - std::vector codecs_; + std::vector send_codecs_; + std::vector recv_codecs_; std::vector channels_; webrtc::Config voe_config_; bool is_dumping_aec_ = false; diff --git a/webrtc/media/engine/webrtcvoiceengine_unittest.cc b/webrtc/media/engine/webrtcvoiceengine_unittest.cc index 2db70d1521..ec7a168f65 100644 --- a/webrtc/media/engine/webrtcvoiceengine_unittest.cc +++ b/webrtc/media/engine/webrtcvoiceengine_unittest.cc @@ -75,7 +75,9 @@ TEST(WebRtcVoiceEngineTestStubLibrary, StartupShutdown) { cricket::FakeWebRtcVoiceEngine voe; EXPECT_FALSE(voe.IsInited()); { - cricket::WebRtcVoiceEngine engine(&adm, nullptr, new FakeVoEWrapper(&voe)); + cricket::WebRtcVoiceEngine engine( + &adm, webrtc::MockAudioDecoderFactory::CreateUnusedFactory(), + new FakeVoEWrapper(&voe)); EXPECT_TRUE(voe.IsInited()); } EXPECT_FALSE(voe.IsInited()); @@ -96,12 +98,13 @@ class WebRtcVoiceEngineTestFake : public testing::Test { explicit WebRtcVoiceEngineTestFake(const char* field_trials) : call_(webrtc::Call::Config()), override_field_trials_(field_trials) { + auto factory = webrtc::MockAudioDecoderFactory::CreateUnusedFactory(); EXPECT_CALL(adm_, AddRef()).WillOnce(Return(0)); EXPECT_CALL(adm_, Release()).WillOnce(Return(0)); EXPECT_CALL(adm_, BuiltInAECIsAvailable()).WillOnce(Return(false)); EXPECT_CALL(adm_, BuiltInAGCIsAvailable()).WillOnce(Return(false)); EXPECT_CALL(adm_, BuiltInNSIsAvailable()).WillOnce(Return(false)); - engine_.reset(new cricket::WebRtcVoiceEngine(&adm_, nullptr, + engine_.reset(new cricket::WebRtcVoiceEngine(&adm_, factory, new FakeVoEWrapper(&voe_))); send_parameters_.codecs.push_back(kPcmuCodec); recv_parameters_.codecs.push_back(kPcmuCodec); @@ -3361,20 +3364,10 @@ TEST_F(WebRtcVoiceEngineTestFake, OnReadyToSendSignalsNetworkState) { // Tests that the library initializes and shuts down properly. TEST(WebRtcVoiceEngineTest, StartupShutdown) { - using testing::_; - using testing::AnyNumber; - // If the VoiceEngine wants to gather available codecs early, that's fine but // we never want it to create a decoder at this stage. - rtc::scoped_refptr factory = - new rtc::RefCountedObject; - ON_CALL(*factory.get(), GetSupportedFormats()) - .WillByDefault(Return(std::vector())); - EXPECT_CALL(*factory.get(), GetSupportedFormats()) - .Times(AnyNumber()); - EXPECT_CALL(*factory.get(), MakeAudioDecoderMock(_, _)).Times(0); - - cricket::WebRtcVoiceEngine engine(nullptr, factory); + cricket::WebRtcVoiceEngine engine( + nullptr, webrtc::MockAudioDecoderFactory::CreateUnusedFactory()); std::unique_ptr call( webrtc::Call::Create(webrtc::Call::Config())); cricket::VoiceMediaChannel* channel = engine.CreateChannel( @@ -3389,7 +3382,8 @@ TEST(WebRtcVoiceEngineTest, StartupShutdownWithExternalADM) { EXPECT_CALL(adm, AddRef()).Times(3).WillRepeatedly(Return(0)); EXPECT_CALL(adm, Release()).Times(3).WillRepeatedly(Return(0)); { - cricket::WebRtcVoiceEngine engine(&adm, nullptr); + cricket::WebRtcVoiceEngine engine( + &adm, webrtc::MockAudioDecoderFactory::CreateUnusedFactory()); std::unique_ptr call( webrtc::Call::Create(webrtc::Call::Config())); cricket::VoiceMediaChannel* channel = engine.CreateChannel( @@ -3400,8 +3394,6 @@ TEST(WebRtcVoiceEngineTest, StartupShutdownWithExternalADM) { } // Tests that the library is configured with the codecs we want. -// TODO(ossu): This test should move into the builtin audio codecs module -// eventually. TEST(WebRtcVoiceEngineTest, HasCorrectCodecs) { // TODO(ossu): These tests should move into a future "builtin audio codecs" // module. @@ -3457,10 +3449,13 @@ TEST(WebRtcVoiceEngineTest, HasCorrectCodecs) { cricket::AudioCodec(0, "", 0, 5000, 1), nullptr)); // Verify the payload id of common audio codecs, including CN, ISAC, and G722. - cricket::WebRtcVoiceEngine engine(nullptr, - webrtc::CreateBuiltinAudioDecoderFactory()); + // TODO(ossu): Why are the payload types of codecs with non-static payload + // type assignments checked here? It shouldn't really matter. + cricket::WebRtcVoiceEngine engine( + nullptr, webrtc::MockAudioDecoderFactory::CreateUnusedFactory()); for (std::vector::const_iterator it = - engine.send_codecs().begin(); it != engine.send_codecs().end(); ++it) { + engine.send_codecs().begin(); + it != engine.send_codecs().end(); ++it) { if (it->name == "CN" && it->clockrate == 16000) { EXPECT_EQ(105, it->id); } else if (it->name == "CN" && it->clockrate == 32000) { @@ -3485,7 +3480,8 @@ TEST(WebRtcVoiceEngineTest, HasCorrectCodecs) { // Tests that VoE supports at least 32 channels TEST(WebRtcVoiceEngineTest, Has32Channels) { - cricket::WebRtcVoiceEngine engine(nullptr, nullptr); + cricket::WebRtcVoiceEngine engine( + nullptr, webrtc::MockAudioDecoderFactory::CreateUnusedFactory()); std::unique_ptr call( webrtc::Call::Create(webrtc::Call::Config())); diff --git a/webrtc/media/media.gyp b/webrtc/media/media.gyp index 1e19a84936..0523b710de 100644 --- a/webrtc/media/media.gyp +++ b/webrtc/media/media.gyp @@ -69,6 +69,8 @@ 'base/videosourcebase.h', 'devices/videorendererfactory.h', 'engine/nullwebrtcvideoengine.h', + 'engine/payload_type_mapper.cc', + 'engine/payload_type_mapper.h', 'engine/simulcast.cc', 'engine/simulcast.h', 'engine/webrtccommon.h', @@ -267,6 +269,7 @@ 'base/videoengine_unittest.h', 'base/videoframe_unittest.h', 'engine/nullwebrtcvideoengine_unittest.cc', + 'engine/payload_type_mapper_unittest.cc', 'engine/simulcast_unittest.cc', 'engine/webrtcmediaengine_unittest.cc', 'engine/webrtcvideocapturer_unittest.cc', diff --git a/webrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory.cc b/webrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory.cc index 48b2f5d437..a7f7404892 100644 --- a/webrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory.cc +++ b/webrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory.cc @@ -130,7 +130,31 @@ NamedDecoderConstructor decoder_constructors[] = { class BuiltinAudioDecoderFactory : public AudioDecoderFactory { public: std::vector GetSupportedFormats() override { - FATAL() << "Not implemented yet!"; + static std::vector formats = { +#ifdef WEBRTC_CODEC_OPUS + { "opus", 48000, 2, { + {"minptime", "10" }, + {"useinbandfec", "1" } + } + }, +#endif +#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) + { "isac", 16000, 1 }, +#endif +#if (defined(WEBRTC_CODEC_ISAC)) + { "isac", 32000, 1 }, +#endif +#ifdef WEBRTC_CODEC_G722 + { "G722", 8000, 1 }, +#endif +#ifdef WEBRTC_CODEC_ILBC + { "iLBC", 8000, 1 }, +#endif + { "PCMU", 8000, 1 }, + { "PCMA", 8000, 1 } + }; + + return formats; } std::unique_ptr MakeAudioDecoder( diff --git a/webrtc/modules/audio_coding/codecs/mock/mock_audio_decoder_factory.h b/webrtc/modules/audio_coding/codecs/mock/mock_audio_decoder_factory.h index 6e5737c89b..f91a26bc10 100644 --- a/webrtc/modules/audio_coding/codecs/mock/mock_audio_decoder_factory.h +++ b/webrtc/modules/audio_coding/codecs/mock/mock_audio_decoder_factory.h @@ -14,6 +14,7 @@ #include #include "testing/gmock/include/gmock/gmock.h" +#include "webrtc/base/scoped_ref_ptr.h" #include "webrtc/modules/audio_coding/codecs/audio_decoder_factory.h" namespace webrtc { @@ -30,6 +31,45 @@ class MockAudioDecoderFactory : public AudioDecoderFactory { MOCK_METHOD2(MakeAudioDecoderMock, void(const SdpAudioFormat& format, std::unique_ptr* return_value)); + + // Creates a MockAudioDecoderFactory with no formats and that may not be + // invoked to create a codec - useful for initializing a voice engine, for + // example. + static rtc::scoped_refptr + CreateUnusedFactory() { + using testing::_; + using testing::AnyNumber; + using testing::Return; + + rtc::scoped_refptr factory = + new rtc::RefCountedObject; + ON_CALL(*factory.get(), GetSupportedFormats()) + .WillByDefault(Return(std::vector())); + EXPECT_CALL(*factory.get(), GetSupportedFormats()).Times(AnyNumber()); + EXPECT_CALL(*factory.get(), MakeAudioDecoderMock(_, _)).Times(0); + return factory; + } + + // Creates a MockAudioDecoderFactory with no formats that may be invoked to + // create a codec any number of times. It will, though, return nullptr on each + // call, since it supports no codecs. + static rtc::scoped_refptr + CreateEmptyFactory() { + using testing::_; + using testing::AnyNumber; + using testing::Return; + using testing::SetArgPointee; + + rtc::scoped_refptr factory = + new rtc::RefCountedObject; + ON_CALL(*factory.get(), GetSupportedFormats()) + .WillByDefault(Return(std::vector())); + EXPECT_CALL(*factory.get(), GetSupportedFormats()).Times(AnyNumber()); + ON_CALL(*factory.get(), MakeAudioDecoderMock(_, _)) + .WillByDefault(SetArgPointee<1>(nullptr)); + EXPECT_CALL(*factory.get(), MakeAudioDecoderMock(_, _)).Times(AnyNumber()); + return factory; + } }; } // namespace webrtc