Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/guide/advanced/video-codecs.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ PeerConnectionFactory factory = PeerConnectionFactory.builder()
| Linux | NVENC on NVIDIA GPUs, otherwise the VA-API encoder of the GPU driver (Intel, AMD) | NVENC on NVIDIA GPUs, otherwise the VA-API encoder of the GPU driver (Intel, AMD) where it encodes H.265 | NVENC on NVIDIA RTX 40 and newer, otherwise the VA-API encoder of the GPU driver where it encodes AV1 (e.g. AMD RDNA3 and newer) |
| macOS | VideoToolbox, as with `DefaultVideoEncoderFactory` | VideoToolbox, on Macs with a hardware HEVC encoder, such as all with Apple silicon | Software |

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. AMD's VA-API encoder takes AV1 from 320x128 up; smaller streams are encoded by libaom.
Hardware encoders produce a single layer, but for the VA-API AV1 encoder, which encodes temporal layers too (`L1T2` and `L1T3`), with a rate control of each layer, so that an SFU can forward a lower frame rate. A stream that asks for spatial layers, or for temporal layers from another hardware encoder, is encoded in software. AMD's VA-API encoder takes AV1 from 320x128 up; smaller streams are encoded by libaom.

VP9 can be encoded in hardware too, with the VP9 encoder of the GPU driver, which Intel GPUs have: through VA-API on Linux, and through Media Foundation on Windows. On Windows 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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
#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"
#include "api/video/video_bitrate_allocation.h"
#include "api/video_codecs/scalability_mode.h"
#include "modules/video_coding/svc/scalable_video_controller.h"

#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <vector>

Expand All @@ -43,9 +47,10 @@ namespace jni
// or the sequence header of AV1. Encoders put them in front of the first
// 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. VP9 frames go out as they come, with the VP9
// information of a single layer, as the Java encoders get it.
// H.265 are those of a stream without layers, unless an AV1 encoder that
// makes temporal layers sets its scalability mode and asks for the layer
// of each frame before it encodes it. VP9 frames go out as they come, with
// the VP9 information of a single layer, as the Java encoders get it.
class EncoderOutputProcessor
{
public:
Expand All @@ -54,6 +59,15 @@ namespace jni
// Forgets the parameter sets, for a stream that starts over.
void Reset();

// Sets the layers of the stream, temporal ones of AV1 only, as in
// L1T3. Returns false for a mode WebRTC has no structure of.
bool SetScalabilityMode(webrtc::ScalabilityMode mode);
// Lets the layers follow the bitrate WebRTC gives each.
void OnRatesUpdated(const webrtc::VideoBitrateAllocation & bitrates);
// The layer of the next frame, which buffers it refers to and which
// it replaces, or nothing if no layer is to be encoded now.
std::optional<webrtc::ScalableVideoController::LayerFrameConfig> NextFrameConfig(bool keyFrame);

// Fills the image with the bitstream of one frame, and the codec
// specific information WebRTC needs to send it. The caller sets
// the size of the image before, which the AV1 frame dependencies
Expand All @@ -64,6 +78,10 @@ namespace jni
// frame lacks parameter sets and there are none to put back.
bool Process(std::span<const uint8_t> bitstream, bool keyFrame, webrtc::EncodedImage & image,
webrtc::CodecSpecificInfo & info);
// Does what the above does, for a frame encoded as the given layer.
bool Process(std::span<const uint8_t> bitstream, bool keyFrame,
const webrtc::ScalableVideoController::LayerFrameConfig & layerFrame, webrtc::EncodedImage & image,
webrtc::CodecSpecificInfo & info);

// The quality scaling settings that suit the codec: the thresholds
// of WebRTC's H.264 encoder, for H.265 as well, those for VP9 of
Expand All @@ -77,7 +95,7 @@ namespace jni
bool ProcessAv1(std::span<const uint8_t> bitstream, bool keyFrame, std::vector<uint8_t> & output);

void FillCodecSpecificInfo(const webrtc::EncodedImage & image, bool keyFrame,
webrtc::CodecSpecificInfo & info);
const webrtc::ScalableVideoController::LayerFrameConfig & layerFrame, webrtc::CodecSpecificInfo & info);

private:
const webrtc::VideoCodecType codec;
Expand All @@ -89,7 +107,8 @@ namespace jni

webrtc::H264BitstreamParser h264Parser;
webrtc::H265BitstreamParser h265Parser;
webrtc::ScalableVideoControllerNoLayering svcController;
webrtc::ScalabilityMode scalabilityMode;
std::unique_ptr<webrtc::ScalableVideoController> svcController;

// The group of frames VP9 describes: one layer, every frame
// referring to the one before.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
#include "api/video_codecs/sdp_video_format.h"
#include "api/video_codecs/video_codec.h"
#include "api/video_codecs/video_encoder.h"
#include "modules/video_coding/svc/scalable_video_controller.h"

#include <va/va.h>
#include <va/va_enc_av1.h>

#include <array>
#include <cstdint>
#include <string>
#include <vector>
Expand All @@ -39,12 +41,16 @@ namespace jni
// drivers provide it on Linux.
//
// It works as FFmpeg's VA-API encoder does without B-frames: synchronous,
// one tile, inter frames that refer to the frame before them only, which
// alternate between two reference slots. The sequence and frame headers
// one tile, inter frames that refer to one frame before them. Without
// layers that is the frame just before; with temporal layers (L1T2, L1T3)
// it is the one of the same layer or a lower one that WebRTC's structure
// of the mode names, so that a receiver can do without the upper layers.
// Each reference buffer of the structure is the reference slot of the
// same number. The sequence and frame headers
// are written here and handed to the driver as packed headers, with where
// the quantizer, loop filter and CDEF parameters are in the frame header,
// which the driver's rate control fills in. Anything that fails is an
// error, upon which libaom takes over.
// which the driver's rate control fills in. Spatial layers, and anything
// that fails, are left to libaom.
class VaapiAv1Encoder : public webrtc::VideoEncoder
{
public:
Expand Down Expand Up @@ -74,10 +80,32 @@ namespace jni
// Reads which headers the driver takes and how it codes them.
void QueryCapabilities();

bool Submit(bool keyFrame);
// What a frame refers to: the slot each of the seven reference
// frames names, the second reference, if any, being golden; the
// slots the frame replaces; and its temporal layer.
struct FrameReferences
{
std::array<uint8_t, 7> refFrameIdx = {};
bool golden = false;
uint8_t refreshFrameFlags = 0;
int temporalId = 0;
};

// Turns the buffers of the layer frame into reference slots.
// Returns false if it refers to a slot that holds no frame, or to
// one beyond those kept.
bool PlanReferences(bool keyFrame, const webrtc::ScalableVideoController::LayerFrameConfig & layerFrame,
FrameReferences & references) const;
// A reconstructed surface that no reference slot holds.
int FreeSurface() const;
// Adds the rate control of the stream, or of each temporal layer.
bool AddRateControl(bool keyFrame);

bool Submit(bool keyFrame, const FrameReferences & references, int surface);

void WriteSequenceHeader(std::vector<uint8_t> & obu) const;
void WriteFrameHeader(std::vector<uint8_t> & obu, FrameHeaderLayout & layout, bool keyFrame) const;
void WriteFrameHeader(std::vector<uint8_t> & obu, FrameHeaderLayout & layout, bool keyFrame,
const FrameReferences & references) const;
// The seq_level_idx the stream's size, frame rate and bitrate fit.
uint8_t Level() const;

Expand All @@ -100,9 +128,16 @@ namespace jni
// The sequence header, kept for the key frames.
std::vector<uint8_t> sequenceHeader;

// Which of the reconstructed surfaces, and so which reference slot,
// holds the current frame.
int current;
// The temporal layers, one without layers, and the bitrate of each,
// counting the layers below it.
int temporalLayers;
std::vector<uint32_t> layerBitratesBps;

// The reconstructed surface each reference slot holds, -1 for
// none: those of the two buffers WebRTC's structures use, up to
// L1T3. A key frame fills the other six slots too, but no frame
// refers to them.
std::array<int, 2> slots;
bool referenceValid;
// The frames since the last key frame, whose low bits are the
// order hint.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
namespace jni
{
// What every VA-API encoder of a single codec needs: a configuration and
// a context with an input surface and two reconstructed ones (the frame
// being encoded and the one it refers to), the output buffer, the
// a context with an input surface and reconstructed ones (by default two:
// the frame being encoded and the one it refers to), the output buffer, the
// parameter buffers of a frame, uploading a frame as NV12, encoding it
// and reading what came out. The codec's encoder fills in the parameters.
//
Expand All @@ -49,12 +49,14 @@ namespace jni

// Creates the session for 4:2:0 at constant bitrate, for pictures
// of the given size, in surfaces of the given size, which is that
// of the picture aligned as the codec needs, and with the
// VA_ENC_PACKED_HEADER_* the encoder hands the driver. Mesa takes
// the size of the coded picture from the context, so the context
// has the picture's.
// of the picture aligned as the codec needs, with the
// VA_ENC_PACKED_HEADER_* the encoder hands the driver, and with
// the given number of reconstructed surfaces. Mesa takes the size
// of the coded picture from the context, so the context has the
// picture's.
bool Create(VAProfile profile, VAEntrypoint entrypoint, uint32_t pictureWidth, uint32_t pictureHeight,
uint32_t surfaceWidth, uint32_t surfaceHeight, uint32_t packedHeaders = VA_ENC_PACKED_HEADER_NONE);
uint32_t surfaceWidth, uint32_t surfaceHeight, uint32_t packedHeaders = VA_ENC_PACKED_HEADER_NONE,
int reconstructedCount = 2);
void Destroy();

VASurfaceID InputSurface() const;
Expand All @@ -75,6 +77,12 @@ namespace jni
// The rate control, frame rate and HRD parameters, for constant
// bitrate with the given initial quantizer.
bool AddRateControl(uint32_t bitrateBps, uint32_t framerate, uint32_t initialQp);
// The same, with a rate control of each temporal layer: their
// bitrates, each counting the layers below it too, and the layer
// of each frame after a key frame, a pattern that repeats, which
// the driver is told along with the rate control of a key frame.
bool AddLayerRateControl(const std::vector<uint32_t> & bitratesBps, uint32_t framerate,
const std::vector<uint32_t> & pattern, bool keyFrame);

// Encodes the input surface with the parameter buffers added, and
// waits for it.
Expand All @@ -92,7 +100,8 @@ namespace jni

VAConfigID config;
VAContextID context;
VASurfaceID surfaces[3];
// The input surface, then the reconstructed ones.
std::vector<VASurfaceID> surfaces;
VABufferID codedBuffer;
std::vector<VABufferID> frameBuffers;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#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/svc/create_scalability_structure.h"
#include "modules/video_coding/svc/scalable_video_controller_no_layering.h"
#include "modules/video_coding/utility/vp9_uncompressed_header_parser.h"
#include "rtc_base/logging.h"

Expand Down Expand Up @@ -119,7 +121,9 @@ namespace jni

EncoderOutputProcessor::EncoderOutputProcessor(webrtc::VideoCodecType codec, const webrtc::SdpVideoFormat & format) :
codec(codec),
packetizationMode(webrtc::H264PacketizationMode::NonInterleaved)
packetizationMode(webrtc::H264PacketizationMode::NonInterleaved),
scalabilityMode(webrtc::ScalabilityMode::kL1T1),
svcController(std::make_unique<webrtc::ScalableVideoControllerNoLayering>())
{
auto mode = format.parameters.find("packetization-mode");

Expand All @@ -136,8 +140,49 @@ namespace jni
gofIndex = 0;
}

bool EncoderOutputProcessor::SetScalabilityMode(webrtc::ScalabilityMode mode)
{
std::unique_ptr<webrtc::ScalableVideoController> controller = webrtc::CreateScalabilityStructure(mode);

if (!controller) {
return false;
}

scalabilityMode = mode;
svcController = std::move(controller);

return true;
}

void EncoderOutputProcessor::OnRatesUpdated(const webrtc::VideoBitrateAllocation & bitrates)
{
svcController->OnRatesUpdated(bitrates);
}

std::optional<webrtc::ScalableVideoController::LayerFrameConfig> EncoderOutputProcessor::NextFrameConfig(
bool keyFrame)
{
auto layerFrames = svcController->NextFrameConfig(/*restart=*/keyFrame);

if (layerFrames.empty()) {
return std::nullopt;
}

return layerFrames[0];
}

bool EncoderOutputProcessor::Process(std::span<const uint8_t> bitstream, bool keyFrame,
webrtc::EncodedImage & image, webrtc::CodecSpecificInfo & info)
{
// A stream without layers has a frame for every one encoded.
auto layerFrame = NextFrameConfig(keyFrame);

return layerFrame && Process(bitstream, keyFrame, *layerFrame, image, info);
}

bool EncoderOutputProcessor::Process(std::span<const uint8_t> bitstream, bool keyFrame,
const webrtc::ScalableVideoController::LayerFrameConfig & layerFrame, webrtc::EncodedImage & image,
webrtc::CodecSpecificInfo & info)
{
std::vector<uint8_t> output;
bool processed;
Expand Down Expand Up @@ -189,7 +234,11 @@ namespace jni
image.qp_ = -1;
}

FillCodecSpecificInfo(image, keyFrame, info);
if (scalabilityMode != webrtc::ScalabilityMode::kL1T1) {
image.SetTemporalIndex(layerFrame.TemporalId());
}

FillCodecSpecificInfo(image, keyFrame, layerFrame, info);

return true;
}
Expand Down Expand Up @@ -335,7 +384,7 @@ namespace jni
}

void EncoderOutputProcessor::FillCodecSpecificInfo(const webrtc::EncodedImage & image, bool keyFrame,
webrtc::CodecSpecificInfo & info)
const webrtc::ScalableVideoController::LayerFrameConfig & layerFrame, webrtc::CodecSpecificInfo & info)
{
info.codecType = codec;

Expand All @@ -350,12 +399,11 @@ namespace jni
// AV1 and H.265 have no codec specific information; WebRTC sends
// 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;
info.generic_frame_info = svcController->OnEncodeDone(layerFrame);
info.scalability_mode = scalabilityMode;

if (keyFrame) {
info.template_structure = svcController.DependencyStructure();
info.template_structure = svcController->DependencyStructure();
info.template_structure->resolutions = {
webrtc::RenderResolution(image._encodedWidth, image._encodedHeight)
};
Expand Down
Loading
Loading