Skip to content

Commit 08a1215

Browse files
committed
feat: decode H.265 and AV1 with NVDEC on Linux
The NVDEC decoders decoded H.264 and VP9. They now decode H.265 (Main profile) and AV1 (profile 0) as well, where the GPU does (AV1 since Ampere), asking the driver which codecs it decodes. NVDEC's parser takes the bitstream, so a codec is mostly a matter of telling it which. NVDEC's AV1 parser takes temporal units, which begin with a temporal delimiter, and WebRTC's AV1 frames come without one, so one is put in front. Frames of AV1 spatial layers above the first go to dav1d, as those of VP9 go to libvpx. H.265 has no software decoder to fall back to: what NVDEC cannot decode is an error, and WebRTC asks for a key frame. With NVENC's H.265, NVIDIA GPUs on Linux now carry H.265 calls both ways.
1 parent 368afbd commit 08a1215

9 files changed

Lines changed: 91 additions & 21 deletions

File tree

‎docs/guide/advanced/video-codecs.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ PeerConnectionFactory factory = PeerConnectionFactory.builder()
6868
| Platform | Hardware decoding |
6969
|---|---|
7070
| Windows | H.264, H.265 (Main profile), AV1 and VP9 (profile 0), on GPUs that decode them, through the Media Foundation decoders of Windows on Direct3D 11 (DXVA) |
71-
| Linux | H.264 and VP9 (profile 0) on NVIDIA GPUs, through NVDEC; VP9 (profile 0) on Intel and AMD GPUs through VA-API, when turned on with `webrtc.hardware.vaapiExperimental` |
71+
| Linux | H.264, H.265 (Main profile), VP9 and AV1 (profile 0) on NVIDIA GPUs that decode them, through NVDEC; VP9 (profile 0) on Intel and AMD GPUs through VA-API, when turned on with `webrtc.hardware.vaapiExperimental` |
7272
| macOS | H.264 through VideoToolbox, as with `DefaultVideoDecoderFactory`; VP9 (profile 0) and H.265 (Main profile) through the decoders of VideoToolbox, on Macs that have them, and AV1 (profile 0) on Apple M3 and newer |
7373

7474
On Linux, NVDEC needs an NVIDIA driver, which provides `libcuda.so.1` and `libnvcuvid.so.1`; both are loaded when the factory is made, so a machine without the driver is not affected. Streams with spatial layers are decoded in software.

‎webrtc-jni/src/main/cpp/include/media/video/codec/nvdec/NvdecLibrary.h‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ namespace jni
5858
{
5959
public:
6060
// Returns the library, or null if there is no NVIDIA driver, no
61-
// CUDA device, or a device that decodes neither H.264 nor VP9.
61+
// CUDA device, or a device that decodes none of H.264, H.265, VP9
62+
// and AV1.
6263
static NvdecLibrary * Get();
6364

6465
NvdecLibrary(const NvdecLibrary &) = delete;
@@ -69,8 +70,12 @@ namespace jni
6970
// The name of the device, e.g. "NVIDIA GeForce RTX 4070".
7071
const std::string & DeviceName() const;
7172

73+
// Whether the device decodes the codec at all, 8 bit 4:2:0. AV1
74+
// only newer GPUs do (since Ampere).
7275
bool SupportsH264() const;
76+
bool SupportsH265() const;
7377
bool SupportsVp9() const;
78+
bool SupportsAv1() const;
7479

7580
// Whether the device decodes the codec, 8 bit 4:2:0, at this
7681
// coded size. Needs the context current.
@@ -100,7 +105,9 @@ namespace jni
100105
CUdevice device = 0;
101106
std::string deviceName;
102107
bool h264 = false;
108+
bool h265 = false;
103109
bool vp9 = false;
110+
bool av1 = false;
104111
};
105112

106113
// Makes a CUDA context current on the calling thread for as long as it

‎webrtc-jni/src/main/cpp/include/media/video/codec/nvdec/NvdecVideoDecoder.h‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131

3232
namespace jni
3333
{
34-
// Decodes H.264 or VP9 profile 0 on the NVDEC engine of an NVIDIA GPU.
34+
// Decodes H.264, H.265 Main, VP9 profile 0 or AV1 profile 0 on the NVDEC
35+
// engine of an NVIDIA GPU.
3536
//
3637
// NVDEC's own parser takes the encoded image, finds the pictures in it and
3738
// calls back in three steps: a sequence, which creates the decoder, a
@@ -44,11 +45,12 @@ namespace jni
4445
// Whatever NVDEC cannot decode, such as another profile or a size the GPU
4546
// does not take, and whatever fails, returns
4647
// WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE, so that the software decoder takes
47-
// over.
48+
// over. H.265 has none, so for it that is an error, and WebRTC asks the
49+
// sender for a key frame.
4850
class NvdecVideoDecoder : public webrtc::VideoDecoder
4951
{
5052
public:
51-
// The codec is H.264 or VP9.
53+
// The codec is H.264, H.265, VP9 or AV1.
5254
NvdecVideoDecoder(NvdecLibrary & library, webrtc::VideoCodecType codec);
5355
~NvdecVideoDecoder() override;
5456

@@ -113,6 +115,9 @@ namespace jni
113115
// The decoded picture in system memory, NV12.
114116
std::vector<uint8_t> nv12;
115117

118+
// An AV1 frame with the temporal delimiter put in front of it.
119+
std::vector<uint8_t> av1Unit;
120+
116121
int64_t counter;
117122
std::map<int64_t, PendingFrame> pendingFrames;
118123
};

‎webrtc-jni/src/main/cpp/include/media/video/codec/nvdec/NvdecVideoDecoderFactory.h‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@
2929

3030
namespace jni
3131
{
32-
// Creates the NVDEC decoders that decode on an NVIDIA GPU, for H.264 and
33-
// VP9, whichever the GPU decodes. It offers them in the formats WebRTC's
34-
// software decoders offer too: H.264 in all its profiles, and VP9 in
35-
// profile 0.
32+
// Creates the NVDEC decoders that decode on an NVIDIA GPU, for H.264,
33+
// H.265, VP9 and AV1, whichever the GPU decodes. It offers them in the
34+
// formats WebRTC's software decoders offer too: H.264 in all its profiles,
35+
// and VP9 and AV1 in profile 0. H.265, which WebRTC has no decoder of its
36+
// own for, is offered in Main profile.
3637
class NvdecVideoDecoderFactory : public webrtc::VideoDecoderFactory
3738
{
3839
public:

‎webrtc-jni/src/main/cpp/src/media/video/codec/linux/LinuxHardwareVideoCodecFactories.cpp‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ namespace jni
4949
{
5050
std::vector<std::unique_ptr<webrtc::VideoDecoderFactory>> factories;
5151

52-
// NVDEC on NVIDIA GPUs; VA-API for the GPUs of Intel and AMD, only if
53-
// asked for, since it has not been tested on such a GPU yet.
52+
// NVDEC on NVIDIA GPUs, for H.264, H.265, VP9 and AV1; VA-API for the
53+
// GPUs of Intel and AMD, only if asked for, since it has not been
54+
// tested on such a GPU yet.
5455
if (auto nvdec = NvdecVideoDecoderFactory::Create()) {
5556
factories.push_back(std::move(nvdec));
5657
}

‎webrtc-jni/src/main/cpp/src/media/video/codec/nvdec/NvdecLibrary.cpp‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,19 +103,22 @@ namespace jni
103103

104104
if (PushContext(context)) {
105105
h264 = QueryCodec(cudaVideoCodec_H264);
106+
h265 = QueryCodec(cudaVideoCodec_HEVC);
106107
vp9 = QueryCodec(cudaVideoCodec_VP9);
108+
av1 = QueryCodec(cudaVideoCodec_AV1);
107109

108110
PopContext();
109111
}
110112

111113
ReleaseContext();
112114

113-
if (!h264 && !vp9) {
114-
RTC_LOG(LS_INFO) << "NVDEC: " << deviceName << " decodes neither H.264 nor VP9";
115+
if (!h264 && !h265 && !vp9 && !av1) {
116+
RTC_LOG(LS_INFO) << "NVDEC: " << deviceName << " decodes none of H.264, H.265, VP9 and AV1";
115117
return false;
116118
}
117119

118-
RTC_LOG(LS_INFO) << "NVDEC available on " << deviceName << ", H.264: " << h264 << ", VP9: " << vp9;
120+
RTC_LOG(LS_INFO) << "NVDEC available on " << deviceName << ", H.264: " << h264 << ", H.265: " << h265
121+
<< ", VP9: " << vp9 << ", AV1: " << av1;
119122

120123
return true;
121124
}
@@ -162,11 +165,21 @@ namespace jni
162165
return h264;
163166
}
164167

168+
bool NvdecLibrary::SupportsH265() const
169+
{
170+
return h265;
171+
}
172+
165173
bool NvdecLibrary::SupportsVp9() const
166174
{
167175
return vp9;
168176
}
169177

178+
bool NvdecLibrary::SupportsAv1() const
179+
{
180+
return av1;
181+
}
182+
170183
bool NvdecLibrary::RetainContext(CUcontext * context) const
171184
{
172185
return api.cuDevicePrimaryCtxRetain(context, device) == CUDA_SUCCESS;

‎webrtc-jni/src/main/cpp/src/media/video/codec/nvdec/NvdecVideoDecoder.cpp‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,26 @@ namespace jni
3131
{
3232
cudaVideoCodec CudaCodecOf(webrtc::VideoCodecType codec)
3333
{
34-
return codec == webrtc::kVideoCodecVP9 ? cudaVideoCodec_VP9 : cudaVideoCodec_H264;
34+
switch (codec) {
35+
case webrtc::kVideoCodecVP9:
36+
return cudaVideoCodec_VP9;
37+
case webrtc::kVideoCodecH265:
38+
return cudaVideoCodec_HEVC;
39+
case webrtc::kVideoCodecAV1:
40+
return cudaVideoCodec_AV1;
41+
default:
42+
return cudaVideoCodec_H264;
43+
}
44+
}
45+
46+
// An AV1 temporal delimiter OBU: type 2, with a size field of 0
47+
// (section 5.6 of the AV1 specification).
48+
constexpr uint8_t kAv1TemporalDelimiter[] = { 0x12, 0x00 };
49+
50+
bool StartsWithTemporalDelimiter(const uint8_t * data, size_t size)
51+
{
52+
// The OBU type is in bits 3 to 6 of the header.
53+
return size > 0 && ((data[0] >> 3) & 0x0F) == 2;
3554
}
3655

3756
// How many entries of a frame that never came out are kept.
@@ -131,6 +150,11 @@ namespace jni
131150
{
132151
return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
133152
}
153+
// The frames of an AV1 spatial layer above the first likewise go to
154+
// dav1d, which decodes them as they come.
155+
if (codec == webrtc::kVideoCodecAV1 && image.SpatialIndex().value_or(0) > 0) {
156+
return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
157+
}
134158

135159
// The parser needs the parameters of the stream, which a key frame
136160
// carries. Until one has come, WebRTC is asked for it.
@@ -164,6 +188,16 @@ namespace jni
164188
packet.payload_size = image.size();
165189
packet.timestamp = timestamp;
166190

191+
// NVDEC's AV1 parser takes temporal units, which begin with a
192+
// temporal delimiter; WebRTC's frames come without one.
193+
if (codec == webrtc::kVideoCodecAV1 && !StartsWithTemporalDelimiter(image.data(), image.size())) {
194+
av1Unit.assign(std::begin(kAv1TemporalDelimiter), std::end(kAv1TemporalDelimiter));
195+
av1Unit.insert(av1Unit.end(), image.data(), image.data() + image.size());
196+
197+
packet.payload = av1Unit.data();
198+
packet.payload_size = av1Unit.size();
199+
}
200+
167201
const CUresult result = library.Api().cuvidParseVideoData(parser, &packet);
168202

169203
if (result != CUDA_SUCCESS || failed) {

‎webrtc-jni/src/main/cpp/src/media/video/codec/nvdec/NvdecVideoDecoderFactory.cpp‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
#include "media/video/codec/nvdec/NvdecVideoDecoderFactory.h"
18+
#include "media/video/codec/H265Format.h"
1819
#include "media/video/codec/nvdec/NvdecVideoDecoder.h"
1920

2021
#include "api/video_codecs/video_codec.h"
@@ -45,9 +46,15 @@ namespace jni
4546
if (library.SupportsH264()) {
4647
formats = webrtc::SupportedH264DecoderCodecs();
4748
}
49+
if (library.SupportsH265()) {
50+
formats.push_back(CreateH265MainFormat());
51+
}
4852
if (library.SupportsVp9()) {
4953
formats.push_back(webrtc::SdpVideoFormat::VP9Profile0());
5054
}
55+
if (library.SupportsAv1()) {
56+
formats.push_back(webrtc::SdpVideoFormat::AV1Profile0());
57+
}
5158

5259
return formats;
5360
}

‎webrtc/src/main/java/dev/onvoid/webrtc/media/video/codec/HardwareVideoDecoderFactory.java‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@
5151
* to libvpx and dav1d. Where there is no hardware decoder, this factory
5252
* decodes like a {@link DefaultVideoDecoderFactory}.
5353
* <p>
54-
* On Linux, H.264 and VP9 (profile 0) are decoded on NVIDIA GPUs with NVDEC,
55-
* loaded from the NVIDIA driver when the factory is made; a machine without
56-
* the driver is not affected. Streams with spatial layers go to libvpx.
57-
* VP9 (profile 0) is decoded with VA-API on the GPUs of Intel and AMD only
58-
* with the system property {@code webrtc.hardware.vaapiExperimental} set to
59-
* {@code true}: the decoder has not been tested on such a GPU yet.
54+
* On Linux, H.264, H.265 (Main profile), VP9 (profile 0) and AV1 (profile 0)
55+
* are decoded on NVIDIA GPUs with NVDEC, each where the GPU decodes it (AV1
56+
* since Ampere), loaded from the NVIDIA driver when the factory is made; a
57+
* machine without the driver is not affected. Streams with spatial layers go
58+
* to libvpx and dav1d. VP9 (profile 0) is decoded with VA-API on the GPUs of
59+
* Intel and AMD only with the system property
60+
* {@code webrtc.hardware.vaapiExperimental} set to {@code true}: the decoder
61+
* has not been tested on such a GPU yet.
6062
* <p>
6163
* The supported codecs are those of a {@link DefaultVideoDecoderFactory}, in
6264
* the same order. H.265 comes after them, only where the hardware decodes it:

0 commit comments

Comments
 (0)