Skip to content

Commit e2fd53e

Browse files
authored
feat: decode HEVC and AV1 in hardware in the media player (#331)
* feat: decode HEVC and AV1 in hardware in the media player The player decoded only H.264 and VP9 on the GPU. FFmpeg is now built with the hardware decoders of HEVC and AV1 as well: Direct3D 11 and 12 on Windows, NVDEC on Linux, VideoToolbox on macOS. FFmpeg's own AV1 decoder has no software path, so AV1 gets dav1d as its software decoder. It is a new submodule (1.5.4), built with Meson before FFmpeg into a static library that is linked into libavcodec. On Windows that is with the static C runtime, which is the one FFmpeg is built with. Its version and options are recorded in components.txt, so changing them rebuilds on its own. Meson and Ninja are new build prerequisites, installed by the CI preparation steps. Cross targets get a Meson cross file made from the same variables FFmpeg is configured with; only windows-x86_64 has been built so far. FFmpeg prefers libdav1d to its native AV1 decoder, and only the native one has hardware configurations, so VideoDecoder picks a decoder that has one when hardware decoding is asked for, and otherwise keeps the preferred one. The licence of dav1d and a notice go into the platform jar. Tests compare every frame decoded in hardware with the software decoder for HEVC and AV1, at 320x240 and 1080p. AV1 has its own property, webrtc.test.hardwareAv1Decoding, since older GPUs cannot decode it. * fix: give Meson an SDK when it builds dav1d on macOS Meson finds the compiler by its path inside the Xcode toolchain, which skips the /usr/bin/cc shim that points SDKROOT at an SDK, so on the macOS ARM64 runner it reported that the compiler cannot compile programs ("library 'System' not found"). The SDK is named for the Meson run now. FFmpeg's own configure gets it from the shim, so SDKROOT is unset again before it runs. * fix: record G.711 into Matroska Matroska has no codec ID of its own for G.711, so avformat_query_codec said no and the recorder left PCMU and PCMA tracks out of .mkv files, although the docs, the error message and a comment all promise that Matroska holds every codec WebRTC sends. The Matroska muxer writes G.711 all the same, in its compatibility mode for Windows audio codecs (A_MS/ACM), as "ffmpeg -c copy" does, so the recorder now lets it. The recorder tests can now send the call's audio in a chosen codec, and record PCMU and PCMA into Matroska, reading them back as such. * feat: decode HEVC in hardware only, never in software The player decoded HEVC in software unless asked for hardware, and fell back to software wherever the hardware had no decoder, did not take a stream, or failed. HEVC is now decoded on the hardware only: a player sends it there whether hardwareDecoding is set or not, and where the hardware cannot decode it, creating the player fails with "Decoder not found", or playback stops with an error. No path opens a software decoder for it. FFmpeg's hevc decoder stays in the build, because the HEVC hwaccels run inside it (configure selects it for every one of them); it parses the headers and hands the slices to the hardware, but VideoDecoder never lets it decode a picture: no software open, no software pixel format, no fall back. The tests compared hardware HEVC with software decoding, which no longer exists. They now compare with luma checksums the software decoder gave before this change, which every conforming decoder matches. A new test fault hides the hardware devices, so that a machine without a GPU decoder is tested on every machine, CI included: HEVC does not open there, while H.264 still falls back to software. * docs: add an HEVC/H.265 disclaimer to the NOTICE files HEVC is decoded through the hardware decoders of the platform only, and the project bundles no software HEVC encoder. The disclaimer says so, and that the FFmpeg libraries of the webrtc-java-media platform jars contain FFmpeg's HEVC decoder, which the hardware decoding runs through, and leaves the license for HEVC playback and encoding to end-users and distributors. It goes into the root NOTICE and into the FFmpeg notice each webrtc-java-media platform jar carries. * build: leave FFmpeg's HEVC decoder to the hwaccels that need it --enable-decoder=hevc put FFmpeg's HEVC decoder into every build, but each HEVC hwaccel selects that decoder itself, so where there is one the decoder comes along without it, and the line only made a difference where there is none: on 32-bit ARM Linux it shipped an HEVC decoder that VideoDecoder never lets decode. Without the line, that build has no HEVC decoder at all, and the others are as before (CONFIG_HEVC_DECODER is still 1 on Windows, through the D3D11 and D3D12 hwaccels). The HEVC disclaimer in the NOTICE files now says which platform jars contain the decoder: those that decode HEVC in hardware, all but 32-bit ARM Linux.
1 parent b0008ff commit e2fd53e

31 files changed

Lines changed: 827 additions & 45 deletions

‎.github/actions/prepare-linux/action.yml‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ runs:
3131
- name: Install required packages
3232
run: |
3333
sudo apt-get update -q
34-
# nasm is what FFmpeg assembles its x86 code with.
35-
sudo apt-get install -y -q binutils cmake git locales lsb-release nasm ninja-build pipewire pipewire-pulse pkg-config python3 python3-setuptools rsync unzip wget xz-utils
34+
# nasm is what FFmpeg assembles its x86 code with, and meson builds
35+
# dav1d, the software decoder of AV1 in the media module.
36+
sudo apt-get install -y -q binutils cmake git locales lsb-release meson nasm ninja-build pipewire pipewire-pulse pkg-config python3 python3-setuptools rsync unzip wget xz-utils
3637
shell: bash
3738

3839
# Chromium Clang to be used with the clang toolchain file. The JNI code

‎.github/actions/prepare-macos/action.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ runs:
1616
# nasm is what FFmpeg assembles its x86 code with, which the Intel
1717
# build needs even though the runner itself is Apple Silicon.
1818
brew install nasm
19+
# Meson and Ninja build dav1d, the software decoder of AV1 in the media
20+
# module, and FFmpeg's configure finds it with pkgconf.
21+
brew install meson ninja pkgconf
1922
shell: bash
2023

2124
- name: Select Xcode version

‎.github/actions/prepare-windows/action.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ runs:
88
- name: Install required packages
99
run: |
1010
choco install ninja
11+
# Meson builds dav1d, the software decoder of AV1 in the media module.
12+
python -m pip install meson
1113
shell: powershell
1214

1315
- name: Install MSYS2 and the FFmpeg build tools

‎.gitmodules‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
path = webrtc-java-media/third-party/ffmpeg
33
url = https://github.com/FFmpeg/FFmpeg.git
44
shallow = true
5+
[submodule "webrtc-java-media/third-party/dav1d"]
6+
path = webrtc-java-media/third-party/dav1d
7+
url = https://code.videolan.org/videolan/dav1d.git
8+
shallow = true

‎NOTICE‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ built from an unmodified FFmpeg release, without --enable-gpl and without
1919
jar carries the license text and a notice naming the FFmpeg version and where
2020
its source is, under META-INF/licenses/ffmpeg.
2121

22+
HEVC/H.265
23+
24+
Disclaimer regarding HEVC/H.265:
25+
This project decodes HEVC/H.265 video through hardware acceleration only, using
26+
the hardware decoders of the operating system or GPU (VideoToolbox, Direct3D
27+
11/12, NVDEC). It does not include, bundle, or distribute any software-based
28+
HEVC encoder (such as x265), and it never decodes HEVC in software. The FFmpeg
29+
libraries in the webrtc-java-media platform jars that decode HEVC in hardware
30+
(all but 32-bit ARM Linux) contain FFmpeg's HEVC decoder, because FFmpeg's
31+
hardware decoding runs through it: it reads the stream's headers and passes the
32+
coded data to the hardware decoder, which decodes the pictures. End-users and distributors are responsible for ensuring that the
33+
underlying operating system or hardware platform holds a valid license for HEVC
34+
playback and encoding.
35+
2236
DOCUMENTATION
2337

2438
WebRTC 1.0: Real-time Communication Between Browsers

‎docs/guide/media/media-files.md‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ Decoding happens entirely in native code. Frames never travel through Java: the
1515

1616
## Adding the Module
1717

18-
The module is part of the normal build, and it builds FFmpeg from a submodule pinned to a release tag, so the submodule has to be present:
18+
The module is part of the normal build, and it builds FFmpeg and dav1d (the software decoder of AV1) from submodules pinned to release tags, so the submodules have to be present:
1919

2020
```shell
21-
git submodule update --init --depth 1 webrtc-java-media/third-party/ffmpeg
21+
git submodule update --init --depth 1 webrtc-java-media/third-party/ffmpeg webrtc-java-media/third-party/dav1d
2222
mvn install
2323
```
2424

25-
Building FFmpeg needs `make` and `nasm`. On Windows they come from MSYS2:
25+
Building FFmpeg needs `make` and `nasm`, and building dav1d needs [Meson](https://mesonbuild.com) (`pip install meson`) and Ninja. On Windows `make` and `nasm` come from MSYS2:
2626

2727
```shell
2828
winget install MSYS2.MSYS2
2929
C:\msys64\usr\bin\bash -lc "pacman -S --needed make nasm diffutils pkgconf"
3030
```
3131

32-
Maven still runs from an ordinary shell; the build enters MSYS2 and the Visual Studio environment on its own. The first build compiles FFmpeg, which takes a while; later builds reuse the install directory.
32+
Maven still runs from an ordinary shell; the build enters MSYS2 and the Visual Studio environment on its own. The first build compiles FFmpeg and dav1d, which takes a while; later builds reuse the install directory.
3333

3434
Once installed, depend on it alongside `webrtc-java`. It takes two entries: one for the Java API, and one for the natives of the platform you are running on.
3535

@@ -206,7 +206,7 @@ It also keeps the native side of the media sources it feeds alive until it is cl
206206

207207
## Decoding in Hardware
208208

209-
Decoding H.264 and VP9 takes a good part of a processor at high resolutions, and for every stream of a camera wall. A player can decode them on the media engine or GPU of the machine instead. It does so when asked to, with a flag on `MediaFileSource` or `MediaPlayer`:
209+
Decoding H.264, H.265/HEVC, VP9 and AV1 takes a good part of a processor at high resolutions, and for every stream of a camera wall. A player can decode them on the media engine or GPU of the machine instead. It does so when asked to, with a flag on `MediaFileSource` or `MediaPlayer`:
210210

211211
```java
212212
// A file whose video is decoded in hardware, where the platform can.
@@ -220,11 +220,13 @@ MediaPlayer player = new MediaPlayer(new MediaReader(path), videoSource, null, t
220220
boolean hardware = source.getPlayer().isHardwareDecoding();
221221
```
222222

223-
The pictures are the same ones software decoding gives; in the tests, every frame of H.264 and VP9 media is compared with its software counterpart. Hardware decoding is off unless asked for.
223+
The pictures are the same ones software decoding gives; in the tests, every frame of media in each of these codecs is compared with its software counterpart (for HEVC, with checksums of the pictures FFmpeg's software decoder gave once). Hardware decoding is off unless asked for, except for H.265/HEVC.
224+
225+
**HEVC is decoded in hardware only.** A player decodes it on the media engine or GPU whether or not the flag is set, and never in software: `isHardwareDecoding()` is always `true` for it. Where the machine has no hardware decoder for HEVC, creating the player fails with an `IOException`; where the hardware does not take a stream (a profile it cannot decode) or fails while playing, playback stops with an error. The fallback below does not apply to it.
224226

225227
- **Platforms:** macOS through VideoToolbox, Windows through Direct3D 12 (Windows 10 version 2004 or later, with a driver that decodes video through it) and otherwise Direct3D 11, and Linux through NVDEC on NVIDIA GPUs (x86-64 and ARM64; the driver provides `libcuda.so.1` and `libnvcuvid.so.1`, which are loaded when a player asks for hardware). The flag is accepted everywhere; where the platform has no hardware decoder, or the FFmpeg build has none for it (32-bit ARM, VA-API on Intel and AMD GPUs), the video is decoded in software, and `isHardwareDecoding()` says so.
226-
- **Codecs:** H.264 and VP9. VP8, MPEG-4, MJPEG and H.265/HEVC are decoded in software.
227-
- **Fallback:** a stream the hardware does not take, such as a profile it cannot decode, is decoded in software without the player noticing more than `isHardwareDecoding()` turning `false`. If the hardware fails before its first picture, the packets it was sent are decoded again in software and none is lost; if it fails after one, decoding goes on in software from the next key frame. A damaged packet is not taken for a hardware failure after the first picture: as with software decoding, playback stops with an error, and the hardware keeps the stream.
228+
- **Codecs:** H.264, H.265/HEVC, VP9 and AV1, where the GPU decodes them: a GPU that is a few years old may lack AV1, and on a Mac only an M3 or later has it. VP8, MPEG-4 and MJPEG are decoded in software, and so is AV1 where there is no GPU decoder for it (it is decoded by dav1d, which is built into the FFmpeg libraries).
229+
- **Fallback** (every codec but HEVC): a stream the hardware does not take, such as a profile it cannot decode, is decoded in software without the player noticing more than `isHardwareDecoding()` turning `false`. If the hardware fails before its first picture, the packets it was sent are decoded again in software and none is lost; if it fails after one, decoding goes on in software from the next key frame. A damaged packet is not taken for a hardware failure after the first picture: as with software decoding, playback stops with an error, and the hardware keeps the stream.
228230
- **What it saves** is processor time, not the copy: a decoded picture is read back from the media engine into system memory, and converted to I420 as WebRTC wants it. On an Apple M2, a 1080p H.264 stream took 11.6 ms of processor time per frame in software and 1.4 ms in hardware, and a 4K stream 35.7 ms against 4.2 ms, about seven to eight times less. Per frame, software on all cores is faster on the clock, which does not matter at playback speed. Windows gains less: on an NVIDIA GeForce RTX 3080 through Direct3D 12, a 1080p H.264 stream took about 2.1 ms of processor time per frame against 3.6 ms in software, and a 4K stream 5.4 ms against 6.6 ms. Direct3D 11 reads the picture back more slowly and took more than software at 4K (12.4 ms), which is why Direct3D 12 comes first. These are figures for one machine each and for synthetic media, not a promise; try it on your own streams before relying on it.
229231

230232
## Playing a Live Stream
@@ -269,7 +271,7 @@ The FFmpeg build is deliberately small, and carries only what this module plays:
269271
| | |
270272
| --- | --- |
271273
| **Containers** | MP4 and MOV, Matroska and WebM, AVI, MPEG-TS, FLV, WAV, MP3, Ogg, FLAC, AAC |
272-
| **Video** | H.264, H.265/HEVC, VP8, VP9, MPEG-4 (including Xvid and DivX), Microsoft MPEG-4 v1 to v3, MJPEG |
274+
| **Video** | H.264, H.265/HEVC (hardware only), VP8, VP9, AV1, MPEG-4 (including Xvid and DivX), Microsoft MPEG-4 v1 to v3, MJPEG |
273275
| **Audio** | AAC, MP3, MP2, AC-3, Opus, Vorbis, FLAC, PCM, MS and IMA ADPCM |
274276

275277
Audio of any rate or layout is resampled to what WebRTC takes, which is 48 kHz 16-bit PCM in mono or stereo. Video that decodes to I420 — almost all 8-bit H.264, VP8, VP9 and MPEG-4 — reaches the encoder without being copied; anything else is converted first.

‎docs/guide/media/media-recording.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ The container follows the file name:
4848

4949
A name FFmpeg does not know gets Matroska. A track whose codec the file cannot hold, VP8 in MP4 for example, is left out and reported as a warning, and the rest is recorded.
5050

51+
Matroska has no codec ID of its own for G.711, so it is stored as Windows audio (`A_MS/ACM`), the way FFmpeg itself stores it. Players built on FFmpeg, such as VLC and mpv, play it; some others do not.
52+
5153
## Following a Recording
5254

5355
A `MediaRecorderListener` hears when the file begins, what was left out, and whether writing failed:

‎webrtc-java-media/README.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ This module is part of the normal build, and it builds FFmpeg from a submodule,
6262
has to be present:
6363

6464
```shell
65-
git submodule update --init --depth 1 webrtc-java-media/third-party/ffmpeg
65+
git submodule update --init --depth 1 webrtc-java-media/third-party/ffmpeg webrtc-java-media/third-party/dav1d
6666
mvn install
6767
```
6868

@@ -77,5 +77,9 @@ winget install MSYS2.MSYS2
7777
C:\msys64\usr\bin\bash -lc "pacman -S --needed make nasm diffutils pkgconf"
7878
```
7979

80+
dav1d, the software decoder of AV1, is built first and linked into FFmpeg. Building it needs Meson
81+
and Ninja (`pip install meson`, with `meson` on the `PATH`; `ninja` is there for the WebRTC build
82+
already).
83+
8084
Maven still runs from an ordinary shell. The build enters MSYS2 and the Visual Studio environment
8185
on its own, because FFmpeg's configure needs a POSIX shell that can also see `cl` and `link`.

‎webrtc-java-media/src/main/cpp/CMakeLists.txt‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,24 @@ add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
171171
"${FFMPEG_NOTICE_FILE}" "${FFMPEG_LICENSES_DIR}/NOTICE.txt"
172172
COMMENT "Collecting the FFmpeg license and notice for the platform jar")
173173

174+
# dav1d, the software decoder of AV1, is linked into libavcodec, and its license
175+
# asks for its text to go along with the binaries.
176+
set(DAV1D_LICENSE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../../third-party/dav1d/COPYING")
177+
178+
if(NOT EXISTS "${DAV1D_LICENSE_FILE}")
179+
message(FATAL_ERROR
180+
"${DAV1D_LICENSE_FILE} is missing, and the platform jar must not ship dav1d "
181+
"without it. Check out the dav1d submodule.")
182+
endif()
183+
184+
set(DAV1D_LICENSES_DIR "${NATIVES_DIR}/META-INF/licenses/dav1d")
185+
186+
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
187+
COMMAND ${CMAKE_COMMAND} -E make_directory "${DAV1D_LICENSES_DIR}"
188+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
189+
"${DAV1D_LICENSE_FILE}" "${DAV1D_LICENSES_DIR}/COPYING"
190+
COMMENT "Collecting the dav1d license for the platform jar")
191+
174192
# The headers FFmpeg was built against for NVDEC have licenses of their own,
175193
# which go along as well.
176194
if(FFMPEG_NVDEC)

0 commit comments

Comments
 (0)