/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MediaMIMETypes.h" #include "gtest/gtest.h" #include "mozilla/Attributes.h" #include "mozilla/Preferences.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/media/webrtc/CodecInfo.h" using namespace mozilla; using mozilla::WebrtcCodecInfo; constexpr const char* kVideoTypes[] = { "video/av1", "video/h264", "video/vp8", "video/vp9", }; constexpr const char* kAudioTypes[] = { "audio/g722", "audio/opus", "audio/pcma", "audio/pcmu", }; constexpr const char* kNonCanonicalMimeTypes[] = { "video/avc", "video/avc1", "video/avc3", }; class MOZ_RAII ScopedPrefSetter { public: ScopedPrefSetter(const char* aPrefName, bool aValue) : mPrefName(aPrefName), mOriginalValue(Preferences::GetBool(aPrefName, false)) { MOZ_ASSERT(NS_IsMainThread()); Preferences::SetBool(mPrefName, aValue); } ~ScopedPrefSetter() { MOZ_ASSERT(NS_IsMainThread()); Preferences::SetBool(mPrefName, mOriginalValue); } ScopedPrefSetter(const ScopedPrefSetter&) = delete; ScopedPrefSetter& operator=(const ScopedPrefSetter&) = delete; ScopedPrefSetter(ScopedPrefSetter&&) = delete; ScopedPrefSetter& operator=(ScopedPrefSetter&&) = delete; private: const char* mPrefName; const bool mOriginalValue; }; class WebRTCCodecInfoTest : public testing::Test { protected: void SetUp() override { // Ensure gfxVars is initialized before tests run. MOZ_ASSERT(NS_IsMainThread()); if (!gfx::gfxVars::IsInitialized()) { gfx::gfxVars::Initialize(); } } // Returns false if the MIME string is unparseable or unsupported. static bool SupportsSWEncode(const WebrtcCodecInfo& aInfo, const char* aMime) { Maybe mime = MakeMediaExtendedMIMEType(aMime); return mime && aInfo.CheckEncodeType(*mime) && (!mime->Type().HasVideoMajorType() || SupportsVideoMimeEncodeForWebrtc(*mime).contains( media::EncodeSupport::SoftwareEncode)); } static bool SupportsSWDecode(const WebrtcCodecInfo& aInfo, const char* aMime) { Maybe mime = MakeMediaExtendedMIMEType(aMime); return mime && aInfo.CheckDecodeType(*mime) && (!mime->Type().HasVideoMajorType() || SupportsVideoMimeDecodeForWebrtc(*mime).contains( media::DecodeSupport::SoftwareDecode)); } static bool SupportsHWEncode(const WebrtcCodecInfo& aInfo, const char* aMime) { Maybe mime = MakeMediaExtendedMIMEType(aMime); return mime && aInfo.CheckEncodeType(*mime) && mime->Type().HasVideoMajorType() && SupportsVideoMimeEncodeForWebrtc(*mime).contains( media::EncodeSupport::HardwareEncode); } static bool SupportsHWDecode(const WebrtcCodecInfo& aInfo, const char* aMime) { Maybe mime = MakeMediaExtendedMIMEType(aMime); return mime && aInfo.CheckDecodeType(*mime) && mime->Type().HasVideoMajorType() && SupportsVideoMimeDecodeForWebrtc(*mime).contains( media::DecodeSupport::HardwareDecode); } // Helper function used to verify audio encode/decode is still working for use // in the video pref tests. static void TestAudioDecodeEncodeSWHW(const WebrtcCodecInfo* aCodecInfo) { for (const auto& type : kAudioTypes) { EXPECT_TRUE(SupportsSWDecode(*aCodecInfo, type)) << "Type failed: " << type; EXPECT_TRUE(SupportsSWEncode(*aCodecInfo, type)) << "Type failed: " << type; EXPECT_FALSE(SupportsHWDecode(*aCodecInfo, type)) << "Type failed: " << type; EXPECT_FALSE(SupportsHWEncode(*aCodecInfo, type)) << "Type failed: " << type; } } }; // Test that invalid MIME types return false for all support queries. TEST_F(WebRTCCodecInfoTest, InvalidMimeType) { const auto codecInfo = WebrtcCodecInfo::Create(); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "")); EXPECT_FALSE(SupportsSWEncode(*codecInfo, "foobar")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "video/fake")); EXPECT_FALSE(SupportsSWEncode(*codecInfo, "/")); EXPECT_FALSE(SupportsSWEncode(*codecInfo, "/fake")); EXPECT_FALSE(SupportsSWEncode(*codecInfo, "fake/")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "video")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "video fake")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "video/no; fake")); EXPECT_FALSE(SupportsSWEncode(*codecInfo, "\xe6\xbc\xa2\xe5\xad\x97")); } // Test that RTP supplementary codecs are filtered as per WPTs. See: // https://searchfox.org/firefox-main/rev/f37efeb9fd346125bfc98d132ae0dea48a1e2584/testing/web-platform/tests/media-capabilities/decodingInfo-webrtc.any.js#221 TEST_F(WebRTCCodecInfoTest, FilterRTPSupplementaryCodecs) { const auto codecInfo = WebrtcCodecInfo::Create(); // RTX - Retransmission (RFC 4588) EXPECT_FALSE(SupportsSWEncode(*codecInfo, "video/rtx")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "video/rtx")); // RED - Redundant encoding (RFC 2198) EXPECT_FALSE(SupportsSWEncode(*codecInfo, "video/red")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "video/red")); EXPECT_FALSE(SupportsSWEncode(*codecInfo, "audio/red")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "audio/red")); // ULPFEC - Forward error correction (RFC 5109) EXPECT_FALSE(SupportsSWEncode(*codecInfo, "video/ulpfec")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "video/ulpfec")); // FlexFEC - Flexible forward error correction EXPECT_FALSE(SupportsSWEncode(*codecInfo, "video/flexfec-03")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "video/flexfec-03")); // Telephone-event - DTMF tones (RFC 4733) EXPECT_FALSE(SupportsSWEncode(*codecInfo, "audio/telephone-event")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "audio/telephone-event")); // CN - Comfort Noise EXPECT_FALSE(SupportsSWEncode(*codecInfo, "audio/CN")); EXPECT_FALSE(SupportsSWDecode(*codecInfo, "audio/CN")); } // Test that audio MIME types are rejected when querying video support // and vice versa. TEST_F(WebRTCCodecInfoTest, AudioVideoMismatch) { const auto codecInfo = WebrtcCodecInfo::Create(); const auto swapMajorType = [](const char* aMime) -> std::string { std::string s(aMime); if (s.starts_with("video/")) { s.replace(0, 5, "audio"); } else if (s.starts_with("audio/")) { s.replace(0, 5, "video"); } return s; }; for (const auto& type : kVideoTypes) { const auto swapped = swapMajorType(type); EXPECT_FALSE(SupportsSWDecode(*codecInfo, swapped.c_str())) << "Type failed: " << swapped; EXPECT_FALSE(SupportsSWEncode(*codecInfo, swapped.c_str())) << "Type failed: " << swapped; } for (const auto& type : kAudioTypes) { const auto swapped = swapMajorType(type); EXPECT_FALSE(SupportsSWDecode(*codecInfo, swapped.c_str())) << "Type failed: " << swapped; EXPECT_FALSE(SupportsSWEncode(*codecInfo, swapped.c_str())) << "Type failed: " << swapped; } } // Test that common WebRTC video codecs are supported (software) TEST_F(WebRTCCodecInfoTest, CommonVideoCodecsSupported) { const auto codecInfo = WebrtcCodecInfo::Create(); for (const auto& type : kVideoTypes) { EXPECT_TRUE(SupportsSWDecode(*codecInfo, type)) << "Type failed: " << type; EXPECT_TRUE(SupportsSWEncode(*codecInfo, type)) << "Type failed: " << type; } } // Test that common WebRTC audio codecs are supported. TEST_F(WebRTCCodecInfoTest, CommonAudioCodecsSupported) { const auto codecInfo = WebrtcCodecInfo::Create(); TestAudioDecodeEncodeSWHW(codecInfo.get()); } // Test that non-canonical WebRTC H264 MIME types are rejected. TEST_F(WebRTCCodecInfoTest, H264NonCanonicalMimeTypes) { const auto codecInfo = WebrtcCodecInfo::Create(); for (const auto& type : kNonCanonicalMimeTypes) { EXPECT_FALSE(SupportsSWEncode(*codecInfo, type)); EXPECT_FALSE(SupportsSWDecode(*codecInfo, type)); } } // Test case insensitivity of MIME type parsing. TEST_F(WebRTCCodecInfoTest, CaseInsensitiveMimeTypes) { const auto codecInfo = WebrtcCodecInfo::Create(); for (const auto& type : kVideoTypes) { nsAutoCString upper(type); ToUpperCase(upper); EXPECT_TRUE(SupportsSWEncode(*codecInfo, upper.get())) << "Type failed: " << upper.get(); EXPECT_TRUE(SupportsSWDecode(*codecInfo, upper.get())) << "Type failed: " << upper.get(); } for (const auto& type : kAudioTypes) { nsAutoCString upper(type); ToUpperCase(upper); EXPECT_TRUE(SupportsSWEncode(*codecInfo, upper.get())) << "Type failed: " << upper.get(); EXPECT_TRUE(SupportsSWDecode(*codecInfo, upper.get())) << "Type failed: " << upper.get(); } } // Test that H.264-specific WebRTC pref blocks HW encode/decode if false TEST_F(WebRTCCodecInfoTest, H264HWBlockedByWebRTCPref) { const ScopedPrefSetter h264Pref("media.webrtc.hw.h264.enabled", false); const auto codecInfo = WebrtcCodecInfo::Create(); EXPECT_FALSE(SupportsHWDecode(*codecInfo, "video/h264")); EXPECT_FALSE(SupportsHWEncode(*codecInfo, "video/h264")); // SW should still work EXPECT_TRUE(SupportsSWDecode(*codecInfo, "video/h264")); EXPECT_TRUE(SupportsSWEncode(*codecInfo, "video/h264")); // Audio shouldn't be affected TestAudioDecodeEncodeSWHW(codecInfo.get()); } // Test that AV1 WebRTC pref disables AV1 support if false TEST_F(WebRTCCodecInfoTest, AV1BlockedByWebRTCPref) { const ScopedPrefSetter av1Pref("media.webrtc.codec.video.av1.enabled", false); const auto codecInfo = WebrtcCodecInfo::Create(); for (const auto& type : kVideoTypes) { if (strcmp(type, "video/av1") == 0) { EXPECT_FALSE(SupportsSWDecode(*codecInfo, type)) << "Type: " << type; EXPECT_FALSE(SupportsSWEncode(*codecInfo, type)) << "Type: " << type; } else { EXPECT_TRUE(SupportsSWDecode(*codecInfo, type)) << "Type: " << type; EXPECT_TRUE(SupportsSWEncode(*codecInfo, type)) << "Type: " << type; } } // Audio shouldn't be affected TestAudioDecodeEncodeSWHW(codecInfo.get()); } // Test that VP9 WebRTC pref disables VP9 support if false TEST_F(WebRTCCodecInfoTest, VP9BlockedByWebRTCPref) { const ScopedPrefSetter vp9Pref("media.peerconnection.video.vp9_enabled", false); const auto codecInfo = WebrtcCodecInfo::Create(); for (const auto& type : kVideoTypes) { if (strcmp(type, "video/vp9") == 0) { EXPECT_FALSE(SupportsSWDecode(*codecInfo, type)) << "Type: " << type; EXPECT_FALSE(SupportsSWEncode(*codecInfo, type)) << "Type: " << type; } else { EXPECT_TRUE(SupportsSWDecode(*codecInfo, type)) << "Type: " << type; EXPECT_TRUE(SupportsSWEncode(*codecInfo, type)) << "Type: " << type; } } // Audio shouldn't be affected TestAudioDecodeEncodeSWHW(codecInfo.get()); } // Test that the H.264 baseline disable pref does not affect non-baseline H264. // Note: baseline profiles specifically cannot be verified here because // CodecInfo does not yet inspect fmtp parameters (Bug 2024767). TEST_F(WebRTCCodecInfoTest, H264BaselineBlockedByWebRTCPref) { const ScopedPrefSetter baselinePref( "media.navigator.video.disable_h264_baseline", true); const auto codecInfo = WebrtcCodecInfo::Create(); // Non-baseline H264 should still be supported EXPECT_TRUE(SupportsSWDecode(*codecInfo, "video/h264")); EXPECT_TRUE(SupportsSWEncode(*codecInfo, "video/h264")); // Audio shouldn't be affected TestAudioDecodeEncodeSWHW(codecInfo.get()); }