From 74846bef9a1612110f2cf357cc7bf124bdde94d6 Mon Sep 17 00:00:00 2001 From: Alex Andres Date: Sun, 11 Oct 2026 14:33:27 +0200 Subject: [PATCH] feat: encode VP9 in hardware on Windows, when turned on Intel GPUs have a Media Foundation VP9 encoder. The hardware encoder factory uses it for VP9 (profile 0), the way it uses the others, when the system property webrtc.hardware.vp9Encoder is true as the factory is created. It is off by default: the encoder has not been tested on an Intel GPU, and once on, it would take over VP9 from libvpx. Without the property, or where there is no VP9 encoder, VP9 is encoded with libvpx, and a hardware encoder that fails to start, or a stream that asks for SVC, falls back to libvpx as well. VP9 is offered by the software encoders anyway, so turning it on changes nothing that is negotiated. EncoderOutputProcessor passes VP9 frames on as they come, with the VP9 information of a single layer that the Java encoders get too (the group of frames with every key frame), parses their QP, and uses the VP9 quality scaling thresholds of the Java encoders. On a machine with an Intel VP9 encoder registered but no Intel GPU, it fails to start and libvpx takes over, which the test of the property saw. --- docs/guide/advanced/video-codecs.md | 8 +++ .../video/codec/EncoderOutputProcessor.h | 14 ++++- .../media/video/codec/HardwareCodecOptions.h | 33 ++++++++++ .../codec/windows/MFVideoEncoderFactory.h | 11 ++-- .../video/codec/EncoderOutputProcessor.cpp | 49 ++++++++++++++- .../video/codec/HardwareCodecOptions.cpp | 63 +++++++++++++++++++ .../video/codec/windows/MFVideoEncoder.cpp | 8 +++ .../codec/windows/MFVideoEncoderFactory.cpp | 20 ++++-- .../WindowsHardwareVideoCodecFactories.cpp | 5 +- .../codec/HardwareVideoEncoderFactory.java | 5 ++ .../HardwareVideoEncoderIntegrationTest.java | 62 +++++++++++++++++- 11 files changed, 262 insertions(+), 16 deletions(-) create mode 100644 webrtc-jni/src/main/cpp/include/media/video/codec/HardwareCodecOptions.h create mode 100644 webrtc-jni/src/main/cpp/src/media/video/codec/HardwareCodecOptions.cpp diff --git a/docs/guide/advanced/video-codecs.md b/docs/guide/advanced/video-codecs.md index d80b970d7..e185733e3 100644 --- a/docs/guide/advanced/video-codecs.md +++ b/docs/guide/advanced/video-codecs.md @@ -32,6 +32,14 @@ PeerConnectionFactory factory = PeerConnectionFactory.builder() Hardware encoders produce a single layer. A stream that asks for SVC, such as an AV1 stream with a scalability mode like `L1T3`, is encoded in software. +VP9 can be encoded in hardware too, on Windows with the Media Foundation VP9 encoder of the GPU driver, which Intel GPUs have. It is off by default, since it has not been tested on such a GPU yet; turn it on with a system property before the factory is created: + +```java +System.setProperty("webrtc.hardware.vp9Encoder", "true"); +``` + +Where the GPU has no VP9 encoder, or the stream asks for SVC, VP9 is still encoded with libvpx. + NVENC needs an NVIDIA driver of version 522 or newer on Windows, 520 or newer on Linux. VA-API needs libva 2 and a driver that encodes H.264, such as Intel's `intel-media-va-driver` (iHD) or Mesa's `mesa-va-drivers` for AMD, and access to a render node in `/dev/dri`. All of them are loaded at run time, so nothing needs to be installed on machines without them. The hardware encoders take over H.264 Constrained Baseline and Baseline with packetization mode 1, and AV1 profile 0, formats the software encoders offer too, so encoding these in hardware never changes what is negotiated. H.265 (Main profile) has no software encoder: it is offered after the other codecs, only where the hardware encodes it, so the codec a peer connection prefers stays the same, and a call uses H.265 only if it is preferred (e.g. with `RTCRtpTransceiver.setCodecPreferences()`) and the other end decodes it. On macOS, VideoToolbox is required to encode H.265 in hardware, and never falls back to its software encoder; a Mac without a hardware HEVC encoder does not offer H.265. When a hardware encoder fails to start, for example because the GPU has no encoder sessions left, or fails while encoding, the stream switches to the next encoder in line (e.g. from NVENC to Media Foundation on Windows), and finally to the software encoder, and continues with a key frame. On a machine without a hardware encoder, the factory encodes like `DefaultVideoEncoderFactory`. diff --git a/webrtc-jni/src/main/cpp/include/media/video/codec/EncoderOutputProcessor.h b/webrtc-jni/src/main/cpp/include/media/video/codec/EncoderOutputProcessor.h index 56720ca04..6b0f8ddf6 100644 --- a/webrtc-jni/src/main/cpp/include/media/video/codec/EncoderOutputProcessor.h +++ b/webrtc-jni/src/main/cpp/include/media/video/codec/EncoderOutputProcessor.h @@ -24,6 +24,7 @@ #include "common_video/h264/h264_bitstream_parser.h" #include "common_video/h265/h265_bitstream_parser.h" #include "modules/video_coding/codecs/h264/include/h264_globals.h" +#include "modules/video_coding/codecs/vp9/include/vp9_globals.h" #include "modules/video_coding/include/video_codec_interface.h" #include "modules/video_coding/svc/scalable_video_controller_no_layering.h" @@ -43,7 +44,8 @@ namespace jni // key frame, not necessarily in front of every one, so they are kept and // put back where missing. The frame dependencies WebRTC needs for AV1 and // H.265 are those of a stream without layers, which is all hardware - // encoders produce here. + // encoders produce here. VP9 frames go out as they come, with the VP9 + // information of a single layer, as the Java encoders get it. class EncoderOutputProcessor { public: @@ -64,8 +66,9 @@ namespace jni webrtc::CodecSpecificInfo & info); // The quality scaling settings that suit the codec: the thresholds - // of WebRTC's H.264 encoder, for H.265 as well, and none for AV1, - // whose QP is not parsed from the bitstream. + // of WebRTC's H.264 encoder, for H.265 as well, those for VP9 of + // the Java encoders, and none for AV1, whose QP is not parsed from + // the bitstream. webrtc::VideoEncoder::ScalingSettings GetScalingSettings() const; private: @@ -87,6 +90,11 @@ namespace jni webrtc::H264BitstreamParser h264Parser; webrtc::H265BitstreamParser h265Parser; webrtc::ScalableVideoControllerNoLayering svcController; + + // The group of frames VP9 describes: one layer, every frame + // referring to the one before. + webrtc::GofInfoVP9 gof; + size_t gofIndex = 0; }; } diff --git a/webrtc-jni/src/main/cpp/include/media/video/codec/HardwareCodecOptions.h b/webrtc-jni/src/main/cpp/include/media/video/codec/HardwareCodecOptions.h new file mode 100644 index 000000000..2b60dcd32 --- /dev/null +++ b/webrtc-jni/src/main/cpp/include/media/video/codec/HardwareCodecOptions.h @@ -0,0 +1,33 @@ +/* + * Copyright 2026 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JNI_WEBRTC_MEDIA_VIDEO_CODEC_HARDWARE_CODEC_OPTIONS_H_ +#define JNI_WEBRTC_MEDIA_VIDEO_CODEC_HARDWARE_CODEC_OPTIONS_H_ + +namespace jni +{ + // The Java system property that turns on hardware VP9 encoding, which is + // off by default: only Intel GPUs encode VP9, and the encoder has not been + // tested on one. + constexpr const char * kHardwareVp9EncoderProperty = "webrtc.hardware.vp9Encoder"; + + // Whether hardware VP9 encoding is turned on: the Java system property + // above is "true", as Boolean.getBoolean has it. Callable from any thread; + // false where Java cannot be asked, for one with an exception pending. + bool IsHardwareVp9EncoderEnabled(); +} + +#endif diff --git a/webrtc-jni/src/main/cpp/include/media/video/codec/windows/MFVideoEncoderFactory.h b/webrtc-jni/src/main/cpp/include/media/video/codec/windows/MFVideoEncoderFactory.h index b107a25d3..05e769ea4 100644 --- a/webrtc-jni/src/main/cpp/include/media/video/codec/windows/MFVideoEncoderFactory.h +++ b/webrtc-jni/src/main/cpp/include/media/video/codec/windows/MFVideoEncoderFactory.h @@ -33,13 +33,15 @@ namespace jni // packetization mode 1, since mode 0 needs each NAL unit to fit a packet, // which hardware encoders cannot be relied on to keep to, and AV1 in // profile 0. H.265, which WebRTC has no encoder of its own for, is offered - // in Main profile. + // in Main profile. VP9 (profile 0), which only Intel GPUs encode, only if + // asked for. class MFVideoEncoderFactory : public webrtc::VideoEncoderFactory { public: // Returns a factory, or null if there is no hardware H.264, H.265 - // or AV1 encoder on this system. - static std::unique_ptr Create(); + // or AV1 encoder on this system, nor a VP9 one where that is + // enabled. + static std::unique_ptr Create(bool vp9Enabled); ~MFVideoEncoderFactory() override = default; @@ -48,12 +50,13 @@ namespace jni const webrtc::SdpVideoFormat & format) override; private: - MFVideoEncoderFactory(bool h264, bool h265, bool av1); + MFVideoEncoderFactory(bool h264, bool h265, bool av1, bool vp9); private: const bool h264; const bool h265; const bool av1; + const bool vp9; }; } diff --git a/webrtc-jni/src/main/cpp/src/media/video/codec/EncoderOutputProcessor.cpp b/webrtc-jni/src/main/cpp/src/media/video/codec/EncoderOutputProcessor.cpp index e4306191e..c541f6f0f 100644 --- a/webrtc-jni/src/main/cpp/src/media/video/codec/EncoderOutputProcessor.cpp +++ b/webrtc-jni/src/main/cpp/src/media/video/codec/EncoderOutputProcessor.cpp @@ -21,6 +21,7 @@ #include "common_video/h264/h264_common.h" #include "common_video/h265/h265_common.h" #include "modules/video_coding/codecs/interface/common_constants.h" +#include "modules/video_coding/utility/vp9_uncompressed_header_parser.h" #include "rtc_base/logging.h" namespace jni @@ -31,6 +32,10 @@ namespace jni constexpr int kLowH264QpThreshold = 24; constexpr int kHighH264QpThreshold = 37; + // Those of the Java VP9 encoders, for a QP of 0 to 255. + constexpr int kLowVp9QpThreshold = 96; + constexpr int kHighVp9QpThreshold = 185; + // AV1 OBU types, section 6.2.2 of the AV1 specification. constexpr uint8_t kObuSequenceHeader = 1; constexpr uint8_t kObuTemporalDelimiter = 2; @@ -121,11 +126,14 @@ namespace jni if (mode == format.parameters.end() || mode->second != "1") { packetizationMode = webrtc::H264PacketizationMode::SingleNalUnit; } + + gof.SetGofInfoVP9(webrtc::TemporalStructureMode::kTemporalStructureMode1); } void EncoderOutputProcessor::Reset() { parameterSets.clear(); + gofIndex = 0; } bool EncoderOutputProcessor::Process(std::span bitstream, bool keyFrame, @@ -141,6 +149,11 @@ namespace jni case webrtc::kVideoCodecH265: processed = ProcessH265(bitstream, keyFrame, output); break; + case webrtc::kVideoCodecVP9: + // No parameter sets: every key frame carries what it needs. + output.assign(bitstream.begin(), bitstream.end()); + processed = !output.empty(); + break; default: processed = ProcessH264(bitstream, keyFrame, output); break; @@ -165,6 +178,10 @@ namespace jni h265Parser.ParseBitstream(encoded); image.qp_ = h265Parser.GetLastSliceQp().value_or(-1); } + else if (codec == webrtc::kVideoCodecVP9) { + int qp = -1; + image.qp_ = webrtc::vp9::GetQp(output.data(), output.size(), &qp) ? qp : -1; + } else { image.qp_ = -1; } @@ -328,7 +345,8 @@ namespace jni } // AV1 and H.265 have no codec specific information; WebRTC sends - // their frame dependencies from these, in the dependency descriptor. + // their frame dependencies, and those of VP9, from these, in the + // dependency descriptor. auto layerFrames = svcController.NextFrameConfig(/*restart=*/keyFrame); info.generic_frame_info = svcController.OnEncodeDone(layerFrames[0]); info.scalability_mode = webrtc::ScalabilityMode::kL1T1; @@ -339,6 +357,32 @@ namespace jni webrtc::RenderResolution(image._encodedWidth, image._encodedHeight) }; } + + if (codec == webrtc::kVideoCodecVP9) { + // VP9 has its own RTP header, which WebRTC fills from these: one + // spatial and one temporal layer, the group of frames sent with + // every key frame. + if (keyFrame) { + gofIndex = 0; + } + + info.codecSpecific.VP9.inter_pic_predicted = !keyFrame; + info.codecSpecific.VP9.flexible_mode = false; + info.codecSpecific.VP9.ss_data_available = keyFrame; + info.codecSpecific.VP9.temporal_idx = webrtc::kNoTemporalIdx; + info.codecSpecific.VP9.temporal_up_switch = true; + info.codecSpecific.VP9.inter_layer_predicted = false; + info.codecSpecific.VP9.gof_idx = static_cast(gofIndex++ % gof.num_frames_in_gof); + info.codecSpecific.VP9.num_spatial_layers = 1; + info.codecSpecific.VP9.first_frame_in_picture = true; + info.codecSpecific.VP9.spatial_layer_resolution_present = keyFrame; + + if (keyFrame) { + info.codecSpecific.VP9.width[0] = image._encodedWidth; + info.codecSpecific.VP9.height[0] = image._encodedHeight; + info.codecSpecific.VP9.gof.CopyGofInfoVP9(gof); + } + } } webrtc::VideoEncoder::ScalingSettings EncoderOutputProcessor::GetScalingSettings() const @@ -348,6 +392,9 @@ namespace jni if (codec == webrtc::kVideoCodecH264 || codec == webrtc::kVideoCodecH265) { return webrtc::VideoEncoder::ScalingSettings(kLowH264QpThreshold, kHighH264QpThreshold); } + if (codec == webrtc::kVideoCodecVP9) { + return webrtc::VideoEncoder::ScalingSettings(kLowVp9QpThreshold, kHighVp9QpThreshold); + } return webrtc::VideoEncoder::ScalingSettings::kOff; } diff --git a/webrtc-jni/src/main/cpp/src/media/video/codec/HardwareCodecOptions.cpp b/webrtc-jni/src/main/cpp/src/media/video/codec/HardwareCodecOptions.cpp new file mode 100644 index 000000000..5e337059d --- /dev/null +++ b/webrtc-jni/src/main/cpp/src/media/video/codec/HardwareCodecOptions.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2026 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media/video/codec/HardwareCodecOptions.h" +#include "JavaUtils.h" + +#include + +namespace jni +{ + bool IsHardwareVp9EncoderEnabled() + { + JNIEnv * env = AttachCurrentThread(); + + // An exception pending is someone else's to handle, and no JNI call + // may be made meanwhile. + if (env == nullptr || env->ExceptionCheck()) { + return false; + } + if (env->PushLocalFrame(4) != JNI_OK) { + env->ExceptionClear(); + return false; + } + + bool enabled = false; + + // java.lang.Boolean comes from the bootstrap class loader, which any + // thread reaches, one without Java frames as well. + jclass booleanClass = env->FindClass("java/lang/Boolean"); + + if (booleanClass != nullptr) { + jmethodID getBoolean = env->GetStaticMethodID(booleanClass, "getBoolean", "(Ljava/lang/String;)Z"); + jstring name = getBoolean != nullptr ? env->NewStringUTF(kHardwareVp9EncoderProperty) : nullptr; + + if (name != nullptr) { + enabled = env->CallStaticBooleanMethod(booleanClass, getBoolean, name) == JNI_TRUE; + } + } + + // Raised here, so cleared here. + if (env->ExceptionCheck()) { + env->ExceptionClear(); + enabled = false; + } + + env->PopLocalFrame(nullptr); + + return enabled; + } +} diff --git a/webrtc-jni/src/main/cpp/src/media/video/codec/windows/MFVideoEncoder.cpp b/webrtc-jni/src/main/cpp/src/media/video/codec/windows/MFVideoEncoder.cpp index 9d4420cc9..5971d372a 100644 --- a/webrtc-jni/src/main/cpp/src/media/video/codec/windows/MFVideoEncoder.cpp +++ b/webrtc-jni/src/main/cpp/src/media/video/codec/windows/MFVideoEncoder.cpp @@ -61,6 +61,8 @@ namespace jni return MFVideoFormat_AV1; case webrtc::kVideoCodecH265: return MFVideoFormat_HEVC; + case webrtc::kVideoCodecVP9: + return MFVideoFormat_VP90; default: return MFVideoFormat_H264; } @@ -311,6 +313,12 @@ namespace jni hr = transform->SetOutputType(outputStreamId, outputType.Get(), 0); } + else if (codec == webrtc::kVideoCodecVP9) { + // 8-bit 4:2:0: VP9 profile 0, what WebRTC offers. + outputType->SetUINT32(MF_MT_VIDEO_PROFILE, eAVEncVP9VProfile_420_8); + + hr = transform->SetOutputType(outputStreamId, outputType.Get(), 0); + } else { // Constrained Baseline is what peers expect, and is a subset of // Baseline too. Older encoders only know Baseline, which peers diff --git a/webrtc-jni/src/main/cpp/src/media/video/codec/windows/MFVideoEncoderFactory.cpp b/webrtc-jni/src/main/cpp/src/media/video/codec/windows/MFVideoEncoderFactory.cpp index 7ef1dd538..2b4519c65 100644 --- a/webrtc-jni/src/main/cpp/src/media/video/codec/windows/MFVideoEncoderFactory.cpp +++ b/webrtc-jni/src/main/cpp/src/media/video/codec/windows/MFVideoEncoderFactory.cpp @@ -47,11 +47,12 @@ namespace jni } } - std::unique_ptr MFVideoEncoderFactory::Create() + std::unique_ptr MFVideoEncoderFactory::Create(bool vp9Enabled) { bool h264 = false; bool h265 = false; bool av1 = false; + bool vp9 = false; try { ComInitializer comInitializer; @@ -60,23 +61,29 @@ namespace jni h264 = HasHardwareEncoder(MFVideoFormat_H264); h265 = HasHardwareEncoder(MFVideoFormat_HEVC); av1 = HasHardwareEncoder(MFVideoFormat_AV1); + vp9 = vp9Enabled && HasHardwareEncoder(MFVideoFormat_VP90); } catch (...) { return nullptr; } - if (!h264 && !h265 && !av1) { + if (vp9Enabled && !vp9) { + RTC_LOG(LS_INFO) << "Hardware VP9 encoding is enabled, but there is no Media Foundation VP9 encoder"; + } + + if (!h264 && !h265 && !av1 && !vp9) { RTC_LOG(LS_INFO) << "No Media Foundation hardware encoder found"; return nullptr; } - return std::unique_ptr(new MFVideoEncoderFactory(h264, h265, av1)); + return std::unique_ptr(new MFVideoEncoderFactory(h264, h265, av1, vp9)); } - MFVideoEncoderFactory::MFVideoEncoderFactory(bool h264, bool h265, bool av1) : + MFVideoEncoderFactory::MFVideoEncoderFactory(bool h264, bool h265, bool av1, bool vp9) : h264(h264), h265(h265), - av1(av1) + av1(av1), + vp9(vp9) { } @@ -96,6 +103,9 @@ namespace jni if (av1) { formats.push_back(webrtc::SdpVideoFormat::AV1Profile0()); } + if (vp9) { + formats.push_back(webrtc::SdpVideoFormat::VP9Profile0()); + } return formats; } diff --git a/webrtc-jni/src/main/cpp/src/media/video/codec/windows/WindowsHardwareVideoCodecFactories.cpp b/webrtc-jni/src/main/cpp/src/media/video/codec/windows/WindowsHardwareVideoCodecFactories.cpp index d651eb551..8800a1ad9 100644 --- a/webrtc-jni/src/main/cpp/src/media/video/codec/windows/WindowsHardwareVideoCodecFactories.cpp +++ b/webrtc-jni/src/main/cpp/src/media/video/codec/windows/WindowsHardwareVideoCodecFactories.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "media/video/codec/HardwareCodecOptions.h" #include "media/video/codec/HardwareVideoDecoderFactory.h" #include "media/video/codec/HardwareVideoEncoderFactory.h" #include "media/video/codec/nvenc/NvencVideoEncoderFactory.h" @@ -32,7 +33,9 @@ namespace jni if (auto nvenc = NvencVideoEncoderFactory::Create()) { factories.push_back(std::move(nvenc)); } - if (auto mediaFoundation = MFVideoEncoderFactory::Create()) { + // VP9 only if asked for: only Intel GPUs encode it, and the encoder + // has not been tested on one. + if (auto mediaFoundation = MFVideoEncoderFactory::Create(IsHardwareVp9EncoderEnabled())) { factories.push_back(std::move(mediaFoundation)); } diff --git a/webrtc/src/main/java/dev/onvoid/webrtc/media/video/codec/HardwareVideoEncoderFactory.java b/webrtc/src/main/java/dev/onvoid/webrtc/media/video/codec/HardwareVideoEncoderFactory.java index 7694822a1..f181aef1f 100644 --- a/webrtc/src/main/java/dev/onvoid/webrtc/media/video/codec/HardwareVideoEncoderFactory.java +++ b/webrtc/src/main/java/dev/onvoid/webrtc/media/video/codec/HardwareVideoEncoderFactory.java @@ -40,6 +40,11 @@ * have an AV1 encoder, with NVENC or Media Foundation, as a single layer; a * stream that asks for SVC is encoded in software. H.265 (Main profile) is * encoded with NVENC or Media Foundation on GPUs that have an H.265 encoder. + * VP9 (profile 0) is encoded with Media Foundation on GPUs that have a VP9 + * encoder, which on Windows are Intel's, but only with the system property + * {@code webrtc.hardware.vp9Encoder} set to {@code true} when the factory is + * created: the encoder has not been tested on such a GPU yet. Without it, VP9 + * is encoded with libvpx. * A hardware encoder that fails to start, for example because the GPU has no * encoder sessions left, or fails while encoding, is replaced by the next one * in line, and finally by the software encoder of the same codec, so a stream diff --git a/webrtc/src/test/java/dev/onvoid/webrtc/HardwareVideoEncoderIntegrationTest.java b/webrtc/src/test/java/dev/onvoid/webrtc/HardwareVideoEncoderIntegrationTest.java index d6a422299..48ea15c8e 100644 --- a/webrtc/src/test/java/dev/onvoid/webrtc/HardwareVideoEncoderIntegrationTest.java +++ b/webrtc/src/test/java/dev/onvoid/webrtc/HardwareVideoEncoderIntegrationTest.java @@ -49,9 +49,11 @@ * A machine without a hardware encoder skips the tests that need one, as CI * runners do. Set the system property {@code webrtc.test.hardwareEncoder} to * {@code true} on a machine that has one, to make those tests fail instead, - * {@code webrtc.test.hardwareAv1Encoder} for a GPU that encodes AV1, and + * {@code webrtc.test.hardwareAv1Encoder} for a GPU that encodes AV1, * {@code webrtc.test.hardwareH265Encoder} for one that encodes and decodes - * H.265, which a call in H.265 needs at both ends, having no software codec. + * H.265, which a call in H.265 needs at both ends, having no software codec, + * and {@code webrtc.test.hardwareVp9Encoder} for one that encodes VP9 (Intel, + * on Windows), which is tested with hardware VP9 encoding turned on. */ @Execution(ExecutionMode.SAME_THREAD) class HardwareVideoEncoderIntegrationTest extends TestBase { @@ -64,6 +66,11 @@ class HardwareVideoEncoderIntegrationTest extends TestBase { private static final boolean HARDWARE_H265_REQUIRED = Boolean.getBoolean("webrtc.test.hardwareH265Encoder"); + private static final boolean HARDWARE_VP9_REQUIRED = Boolean.getBoolean("webrtc.test.hardwareVp9Encoder"); + + /** The property that turns on hardware VP9 encoding, which is off by default. */ + private static final String VP9_ENCODER_PROPERTY = "webrtc.hardware.vp9Encoder"; + private static final String OS = System.getProperty("os.name").toLowerCase(Locale.ROOT); /** @@ -79,6 +86,10 @@ class HardwareVideoEncoderIntegrationTest extends TestBase { private static final Predicate H265 = codec -> "H265".equalsIgnoreCase(codec.getName()); + private static final Predicate VP9 = codec -> + "VP9".equalsIgnoreCase(codec.getName()) + && "0".equals(codec.getSDPFmtp().getOrDefault("profile-id", "0")); + @Test void hardwareKeepsCodecs() { @@ -248,6 +259,53 @@ private PeerConnectionFactory hardwareCallFactory() { .build(); } + @Test + void vp9HardwareEncodingIsOptIn() throws Exception { + assumeTrue(OS.contains("win") || OS.contains("linux"), + "hardware encoders are implemented on Windows and Linux only"); + assumeTrue(System.getProperty(VP9_ENCODER_PROPERTY) == null, VP9_ENCODER_PROPERTY + " is set"); + + PeerConnectionFactory hardware = hardwareFactory(); + + try { + // Without the property, VP9 stays with libvpx on every GPU. + String implementation = encoderImplementation(hardware, VP9); + + assertTrue(implementation.contains("libvpx"), implementation); + } + finally { + hardware.dispose(); + } + } + + @Test + void hardwareEncodesVp9WhenEnabled() throws Exception { + assumeTrue(OS.contains("win"), "hardware VP9 encoding is implemented on Windows only"); + + String previous = System.setProperty(VP9_ENCODER_PROPERTY, "true"); + + try { + PeerConnectionFactory hardware = hardwareFactory(); + + try { + // Only Intel GPUs encode VP9; elsewhere libvpx does, and the + // test is skipped unless hardware is required. + assertHardware(encoderImplementation(hardware, VP9), HARDWARE_VP9_REQUIRED); + } + finally { + hardware.dispose(); + } + } + finally { + if (previous == null) { + System.clearProperty(VP9_ENCODER_PROPERTY); + } + else { + System.setProperty(VP9_ENCODER_PROPERTY, previous); + } + } + } + private static boolean offersH265(List codecs) { return codecs.stream().anyMatch(codec -> "H265".equalsIgnoreCase(codec.getName())); }