diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc index 0d47191ee..58874093a 100644 --- a/talk/app/webrtc/webrtcsdp.cc +++ b/talk/app/webrtc/webrtcsdp.cc @@ -71,6 +71,7 @@ using cricket::kCodecParamUseInbandFec; using cricket::kCodecParamSctpProtocol; using cricket::kCodecParamSctpStreams; using cricket::kCodecParamMaxAverageBitrate; +using cricket::kCodecParamMaxPlaybackRate; using cricket::kCodecParamAssociatedPayloadType; using cricket::kWildcardPayloadType; using cricket::MediaContentDescription; @@ -1534,7 +1535,8 @@ bool IsFmtpParam(const std::string& name) { kCodecParamStereo, kCodecParamUseInbandFec, kCodecParamStartBitrate, kCodecParamMaxBitrate, kCodecParamMinBitrate, kCodecParamMaxQuantization, kCodecParamSctpProtocol, kCodecParamSctpStreams, - kCodecParamMaxAverageBitrate, kCodecParamAssociatedPayloadType + kCodecParamMaxAverageBitrate, kCodecParamMaxPlaybackRate, + kCodecParamAssociatedPayloadType }; for (size_t i = 0; i < ARRAY_SIZE(kFmtpParams); ++i) { if (_stricmp(name.c_str(), kFmtpParams[i]) == 0) { diff --git a/talk/media/base/constants.cc b/talk/media/base/constants.cc index cd10ef75f..19a960f35 100644 --- a/talk/media/base/constants.cc +++ b/talk/media/base/constants.cc @@ -59,6 +59,7 @@ const char kCodecParamSPropStereo[] = "sprop-stereo"; const char kCodecParamStereo[] = "stereo"; const char kCodecParamUseInbandFec[] = "useinbandfec"; const char kCodecParamMaxAverageBitrate[] = "maxaveragebitrate"; +const char kCodecParamMaxPlaybackRate[] = "maxplaybackrate"; const char kCodecParamSctpProtocol[] = "protocol"; const char kCodecParamSctpStreams[] = "streams"; @@ -72,6 +73,7 @@ const int kOpusDefaultMinPTime = 3; const int kOpusDefaultSPropStereo = 0; const int kOpusDefaultStereo = 0; const int kOpusDefaultUseInbandFec = 0; +const int kOpusDefaultMaxPlaybackRate = 48000; const int kPreferredMaxPTime = 60; const int kPreferredMinPTime = 10; diff --git a/talk/media/base/constants.h b/talk/media/base/constants.h index 5ac1be2b6..5168acb89 100644 --- a/talk/media/base/constants.h +++ b/talk/media/base/constants.h @@ -62,6 +62,7 @@ extern const char kCodecParamSPropStereo[]; extern const char kCodecParamStereo[]; extern const char kCodecParamUseInbandFec[]; extern const char kCodecParamMaxAverageBitrate[]; +extern const char kCodecParamMaxPlaybackRate[]; extern const char kCodecParamSctpProtocol[]; extern const char kCodecParamSctpStreams[]; @@ -79,6 +80,8 @@ extern const int kOpusDefaultMinPTime; extern const int kOpusDefaultSPropStereo; extern const int kOpusDefaultStereo; extern const int kOpusDefaultUseInbandFec; +extern const int kOpusDefaultMaxPlaybackRate; + // Prefered values in this code base. Note that they may differ from the default // values in http://tools.ietf.org/html/draft-spittka-payload-rtp-opus-03 // Only frames larger or equal to 10 ms are currently supported in this code diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h index fe443904d..c984a01da 100644 --- a/talk/media/webrtc/fakewebrtcvoiceengine.h +++ b/talk/media/webrtc/fakewebrtcvoiceengine.h @@ -61,6 +61,12 @@ static const int kFakeDeviceId = 0; static const int kFakeDeviceId = 1; #endif +static const int kOpusBandwidthNb = 4000; +static const int kOpusBandwidthMb = 6000; +static const int kOpusBandwidthWb = 8000; +static const int kOpusBandwidthSwb = 12000; +static const int kOpusBandwidthFb = 20000; + // Verify the header extension ID, if enabled, is within the bounds specified in // [RFC5285]: 1-14 inclusive. #define WEBRTC_CHECK_HEADER_EXTENSION_ID(enable, id) \ @@ -180,6 +186,7 @@ class FakeWebRtcVoiceEngine file(false), vad(false), codec_fec(false), + max_encoding_bandwidth(0), red(false), nack(false), media_processor_registered(false), @@ -209,6 +216,7 @@ class FakeWebRtcVoiceEngine bool file; bool vad; bool codec_fec; + int max_encoding_bandwidth; bool red; bool nack; bool media_processor_registered; @@ -305,6 +313,9 @@ class FakeWebRtcVoiceEngine bool GetCodecFEC(int channel) { return channels_[channel]->codec_fec; } + int GetMaxEncodingBandwidth(int channel) { + return channels_[channel]->max_encoding_bandwidth; + } bool GetNACK(int channel) { return channels_[channel]->nack; } @@ -624,10 +635,11 @@ class FakeWebRtcVoiceEngine } WEBRTC_STUB(GetVADStatus, (int channel, bool& enabled, webrtc::VadModes& mode, bool& disabledDTX)); + #ifdef USE_WEBRTC_DEV_BRANCH WEBRTC_FUNC(SetFECStatus, (int channel, bool enable)) { WEBRTC_CHECK_CHANNEL(channel); - if (strcmp(channels_[channel]->send_codec.plname, "opus")) { + if (_stricmp(channels_[channel]->send_codec.plname, "opus") != 0) { // Return -1 if current send codec is not Opus. // TODO(minyue): Excludes other codecs if they support inband FEC. return -1; @@ -640,6 +652,25 @@ class FakeWebRtcVoiceEngine enable = channels_[channel]->codec_fec; return 0; } + + WEBRTC_FUNC(SetOpusMaxPlaybackRate, (int channel, int frequency_hz)) { + WEBRTC_CHECK_CHANNEL(channel); + if (_stricmp(channels_[channel]->send_codec.plname, "opus") != 0) { + // Return -1 if current send codec is not Opus. + return -1; + } + if (frequency_hz <= 8000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthNb; + else if (frequency_hz <= 12000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthMb; + else if (frequency_hz <= 16000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthWb; + else if (frequency_hz <= 24000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthSwb; + else + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthFb; + return 0; + } #endif // USE_WEBRTC_DEV_BRANCH // webrtc::VoEDtmf diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc index 90ca8cbff..3d5d9ee66 100644 --- a/talk/media/webrtc/webrtcvoiceengine.cc +++ b/talk/media/webrtc/webrtcvoiceengine.cc @@ -120,6 +120,7 @@ static const int kOpusStereoBitrate = 64000; // Opus bitrate should be in the range between 6000 and 510000. static const int kOpusMinBitrate = 6000; static const int kOpusMaxBitrate = 510000; + // Default audio dscp value. // See http://tools.ietf.org/html/rfc2474 for details. // See also http://tools.ietf.org/html/draft-jennings-rtcweb-qos-00 @@ -404,6 +405,7 @@ static bool IsOpusStereoEnabled(const AudioCodec& codec) { return codec.GetParam(kCodecParamStereo, &value) && value == 1; } +// TODO(minyue): Clamp bitrate when invalid. static bool IsValidOpusBitrate(int bitrate) { return (bitrate >= kOpusMinBitrate && bitrate <= kOpusMaxBitrate); } @@ -430,6 +432,59 @@ static bool IsOpusFecEnabled(const AudioCodec& codec) { return codec.GetParam(kCodecParamUseInbandFec, &value) && value == 1; } +// Returns kOpusDefaultPlaybackRate if params[kCodecParamMaxPlaybackRate] is not +// defined. Returns the value of params[kCodecParamMaxPlaybackRate] otherwise. +static int GetOpusMaxPlaybackRate(const AudioCodec& codec) { + int value; + if (codec.GetParam(kCodecParamMaxPlaybackRate, &value)) { + return value; + } + return kOpusDefaultMaxPlaybackRate; +} + +static void GetOpusConfig(const AudioCodec& codec, webrtc::CodecInst* voe_codec, + bool* enable_codec_fec, int* max_playback_rate) { + *enable_codec_fec = IsOpusFecEnabled(codec); + *max_playback_rate = GetOpusMaxPlaybackRate(codec); + + // If OPUS, change what we send according to the "stereo" codec + // parameter, and not the "channels" parameter. We set + // voe_codec.channels to 2 if "stereo=1" and 1 otherwise. If + // the bitrate is not specified, i.e. is zero, we set it to the + // appropriate default value for mono or stereo Opus. + + // TODO(minyue): The determination of bit rate might take the maximum playback + // rate into account. + + if (IsOpusStereoEnabled(codec)) { + voe_codec->channels = 2; + if (!IsValidOpusBitrate(codec.bitrate)) { + if (codec.bitrate != 0) { + LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" + << codec.bitrate + << ") with default opus stereo bitrate: " + << kOpusStereoBitrate; + } + voe_codec->rate = kOpusStereoBitrate; + } + } else { + voe_codec->channels = 1; + if (!IsValidOpusBitrate(codec.bitrate)) { + if (codec.bitrate != 0) { + LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" + << codec.bitrate + << ") with default opus mono bitrate: " + << kOpusMonoBitrate; + } + voe_codec->rate = kOpusMonoBitrate; + } + } + int bitrate_from_params = GetOpusBitrateFromParams(codec); + if (bitrate_from_params != 0) { + voe_codec->rate = bitrate_from_params; + } +} + void WebRtcVoiceEngine::ConstructCodecs() { LOG(LS_INFO) << "WebRtc VoiceEngine codecs:"; int ncodecs = voe_wrapper_->codec()->NumOfCodecs(); @@ -1993,6 +2048,10 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( bool nack_enabled = nack_enabled_; bool enable_codec_fec = false; + // max_playback_rate <= 0 will not trigger setting of maximum encoding + // bandwidth. + int max_playback_rate = 0; + // Set send codec (the first non-telephone-event/CN codec) for (std::vector::const_iterator it = codecs.begin(); it != codecs.end(); ++it) { @@ -2009,40 +2068,6 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( continue; } - // If OPUS, change what we send according to the "stereo" codec - // parameter, and not the "channels" parameter. We set - // voe_codec.channels to 2 if "stereo=1" and 1 otherwise. If - // the bitrate is not specified, i.e. is zero, we set it to the - // appropriate default value for mono or stereo Opus. - if (IsOpus(*it)) { - if (IsOpusStereoEnabled(*it)) { - voe_codec.channels = 2; - if (!IsValidOpusBitrate(it->bitrate)) { - if (it->bitrate != 0) { - LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" - << it->bitrate - << ") with default opus stereo bitrate: " - << kOpusStereoBitrate; - } - voe_codec.rate = kOpusStereoBitrate; - } - } else { - voe_codec.channels = 1; - if (!IsValidOpusBitrate(it->bitrate)) { - if (it->bitrate != 0) { - LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" - << it->bitrate - << ") with default opus mono bitrate: " - << kOpusMonoBitrate; - } - voe_codec.rate = kOpusMonoBitrate; - } - } - int bitrate_from_params = GetOpusBitrateFromParams(*it); - if (bitrate_from_params != 0) { - voe_codec.rate = bitrate_from_params; - } - } // We'll use the first codec in the list to actually send audio data. // Be sure to use the payload type requested by the remote side. @@ -2072,8 +2097,11 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( } else { send_codec = voe_codec; nack_enabled = IsNackEnabled(*it); - // For Opus as the send codec, we enable inband FEC if requested. - enable_codec_fec = IsOpus(*it) && IsOpusFecEnabled(*it); + // For Opus as the send codec, we are to enable inband FEC if requested + // and set maximum playback rate. + if (IsOpus(*it)) { + GetOpusConfig(*it, &send_codec, &enable_codec_fec, &max_playback_rate); + } } found_send_codec = true; break; @@ -2107,6 +2135,21 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( #endif // USE_WEBRTC_DEV_BRANCH } + // maxplaybackrate should be set after SetSendCodec. + if (max_playback_rate > 0) { + LOG(LS_INFO) << "Attempt to set maximum playback rate to " + << max_playback_rate + << " Hz on channel " + << channel; +#ifdef USE_WEBRTC_DEV_BRANCH + // (max_playback_rate + 1) >> 1 is to obtain ceil(max_playback_rate / 2.0). + if (engine()->voe()->codec()->SetOpusMaxPlaybackRate( + channel, max_playback_rate) == -1) { + LOG(LS_WARNING) << "Could not set maximum playback rate."; + } +#endif + } + // Always update the |send_codec_| to the currently set send codec. send_codec_.reset(new webrtc::CodecInst(send_codec)); diff --git a/talk/media/webrtc/webrtcvoiceengine_unittest.cc b/talk/media/webrtc/webrtcvoiceengine_unittest.cc index f5b8260f0..b044e924c 100644 --- a/talk/media/webrtc/webrtcvoiceengine_unittest.cc +++ b/talk/media/webrtc/webrtcvoiceengine_unittest.cc @@ -1152,7 +1152,6 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecNoOpusFec) { int channel_num = voe_.GetLastChannel(); std::vector codecs; codecs.push_back(kOpusCodec); - codecs[0].bitrate = 0; EXPECT_TRUE(channel_->SetSendCodecs(codecs)); EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); } @@ -1229,6 +1228,159 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecIsacWithParamNoFec) { EXPECT_TRUE(channel_->SetSendCodecs(codecs)); EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); } + +// Test that Opus FEC status can be changed. +TEST_F(WebRtcVoiceEngineTestFake, ChangeOpusFecStatus) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); + codecs[0].params["useinbandfec"] = "1"; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(voe_.GetCodecFEC(channel_num)); +} + +// Test maxplaybackrate <= 8000 triggers Opus narrow band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateNb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 8000); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthNb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 8000 < maxplaybackrate <= 12000 triggers Opus medium band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateMb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 8001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthMb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 12000 < maxplaybackrate <= 16000 triggers Opus wide band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateWb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 12001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthWb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 16000 < maxplaybackrate <= 24000 triggers Opus super wide band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateSwb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 16001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthSwb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 24000 < maxplaybackrate triggers Opus full band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateFb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 24001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthFb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test Opus that without maxplaybackrate, default playback rate is used. +TEST_F(WebRtcVoiceEngineTestFake, DefaultOpusMaxPlaybackRate) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthFb, + voe_.GetMaxEncodingBandwidth(channel_num)); +} + +// Test the with non-Opus, maxplaybackrate has no effect. +TEST_F(WebRtcVoiceEngineTestFake, SetNonOpusMaxPlaybackRate) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kIsacCodec); + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 32000); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(0, voe_.GetMaxEncodingBandwidth(channel_num)); +} + +// Test maxplaybackrate can be set on two streams. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateOnTwoStreams) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + // Default bandwidth is 24000. + EXPECT_EQ(cricket::kOpusBandwidthFb, + voe_.GetMaxEncodingBandwidth(channel_num)); + + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 8000); + + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthNb, + voe_.GetMaxEncodingBandwidth(channel_num)); + + channel_->AddSendStream(cricket::StreamParams::CreateLegacy(kSsrc2)); + channel_num = voe_.GetLastChannel(); + EXPECT_EQ(cricket::kOpusBandwidthNb, + voe_.GetMaxEncodingBandwidth(channel_num)); +} #endif // USE_WEBRTC_DEV_BRANCH // Test that we can apply CELT with stereo mode but fail with mono mode.