diff --git a/.cargo/config.toml b/.cargo/config.toml index 2dc4a5047..f7de21c89 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -14,4 +14,7 @@ rustflags = ["-C", "link-args=-ObjC"] rustflags = ["-C", "link-args=-ObjC"] [target.aarch64-apple-ios-sim] -rustflags = ["-C", "link-args=-ObjC"] \ No newline at end of file +rustflags = ["-C", "link-args=-ObjC"] + +[env] +LK_CUSTOM_WEBRTC = { value = "webrtc-sys/libwebrtc/linux-x64-release", relative = true } diff --git a/Cargo.toml b/Cargo.toml index 47c7ce8f7..ba4986681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,9 @@ members = [ "webrtc-sys", "webrtc-sys/build", ] + +[profile.release] +opt-level = 3 # Maximum optimization (0-3, or "s" for size, "z" for even smaller size) +lto = "fat" # Link Time Optimization (LTO): "thin" (faster) or "fat" (better optimization) +codegen-units = 1 # Improves performance by reducing parallel compilation +debug = false # Remove debug symbols for smaller binaries. Set this to true for profiling with debug symbols diff --git a/README.md b/README.md index e305bd727..32cf36553 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,80 @@ - +# ANYbotics forked version of LiveKit Rust SDK +The Rust SDK is using the Google WebRTC repository. The Rust SDK repository has been modified for patching WebRTC. The patches add support for accelerating the encoding process on Intel GPUs, by utilizing the libvpl, vaapi and one of the two Intel GPU runtimes (MediaSDK or Intel VPL GPU RT). +During initialization of WebRTC encoders it is checked whether HW acceleration is possible and if HW acceleration was initialized successfuly. If so, the HW accelerated encoder will take place automatically. Otherwise, the software implementation is used. +**Note**: The design that includes the accelerated encoder implementation is not ideal, however the goal was to modify the Livekit stack as less as possible. + +To make use of the accelerated version of WebRTC, we need to build Livekit from source. To achieve this we need to execute the `/webrtc-sys/libwebrtc/build_linux.sh`. This script checks if the WebRTC repository has already been cloned locally. If not, it fetches it, along with all each submodules and applies some Livekit patches on it. Livekit uses a certain WebRTC version, and not the latest one. +To fetch and build WebRTC: +``` +./build_linux.sh --arch x64 --profile release +``` +In case WebRTC is already present locally, after executing the above script, some warning will be shown regarding the failure of Livekit patches. That's because the patches have already been applied. +Once WebRTC is present locally, we can apply our patches. Navigate to the root of the repository and execute: + +``` +apply_vpl_patches.sh +``` + +The implementation of the accelerated encoder is part of the Rust SDK repo, which we have forked, and it is under `libwebrtc-hw`. Ideally the implementation would be part of the WebRTC repository, but it is more complicated. WebRTC repository is huge and includes a lot of sub-modules, which we would have to fork. For now, Rust SDK repository has all the required changes to use Livekit with accelerated encoding. + +Once we have WebRTC patched and built, a static library is generated. The next step is to build the Rust part and link it against the generated static library. +Under the root folder execute: +``` +cargo clean +cargo build --release +``` +Whenever we build again WebRTC, we need to do a cargo clean and rebuild. +At this point a `liblivekit_ffi.so` has been generated, and our application needs to make use of it, to have our accelerated version. To achieve this, we need to expose its path to an environment variable. Along with this environment variable, we need to expose a couple of other ones as well and the application should start in a context that can parse them: +``` +export LIVEKIT_URL=wss://192.168.0.6/webrtc/ +export LIVEKIT_API_KEY=ads-admin +export LIVEKIT_API_SECRET=livekit-server-secret-for-ads-server +export LIVEKIT_LIB_PATH="/home/integration/gpu_acceleration_ws/anybotics-python-sdks/livekit-rtc/rust-sdks/target/release/liblivekit_ffi.so" +export LD_LIBRARY_PATH=/home/integration/libvpl/_build:$LD_LIBRARY_PATH +export LIBVA_DRIVER_NAME=iHD +export LIBVA_DRIVERS_PATH=/home/integration/media-driver-workspace/build_media/media_driver +``` +The above values are indicative. +**LIVEKIT_URL** should include the IP of the desired pc. +**LIVEKIT_API_KEY** and **LIVEKIT_API_SECRET** are the ones that we use to generate a token. +**LIVEKIT_LIB_PATH** should be set accordingly, depending on where we have install the `liblivekit_ffi.so`. +**LD_LIBRARY_PATH** exposes the Intel libvpl and we need to set the path to the installed location. +**LIBVA_DRIVER_NAME** indicates the Intel driver. iHD is the appropriate one for our HW on anymal D. +**LIBVA_DRIVERS_PATH** exposes the path in which we have installed the Intel runtimes (MediaSDK or Intel® VPL) + + +## Updating patches +To update the patches, navigate to `webrtc-sys/libwebrtc/src` and execute +``` +git diff original_commit new_commit --src-prefix=org/webrtc-sys/libwebrtc/src/ --dst-prefix=update/webrtc-sys/libwebrtc/src/ ./path/file > ./../../../libwebrtc-patches/file.patch +``` + +## Developing and testing +If the development takes place on a PC, we need to start a local server with +``` +livekit-server --dev +``` +Use the Livekit web client to receive the stream +``` +https://meet.livekit.io/?tab=custom +``` +Add the required URL of the server. +In case we test on a PC, use the loopback IP +``` +ws://localhost:7880 +``` +In case we test on an anymal use the corresponding IP. The example below is from dobby: +``` +wss://192.168.0.6/webrtc/ +``` + +For a token we need to use the Livekit CLI: +``` +lk token create --api-key ads-admin --api-secret livekit-server-secret-for-ads-server --join --room dobby --identity test_user --valid-for 24h +``` +adapt the arguments accordingly. - - - - The LiveKit icon, the name of the repository and some sample code in the background. - - # 📹🎙️🦀 Rust Client SDK for LiveKit diff --git a/apply_vpl_patches.sh b/apply_vpl_patches.sh new file mode 100755 index 000000000..ce86a21e8 --- /dev/null +++ b/apply_vpl_patches.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +git apply -p1 libwebrtc-patches/h264_encoder_impl_cc.patch +if [ $? -eq 0 ]; then + echo "h264_encoder_impl_cc.patch applied successfully." +else + echo "Failed to apply h264_encoder_impl_cc.patch." + exit 1 +fi + +git apply -p1 libwebrtc-patches/h264_encoder_impl_h.patch +if [ $? -eq 0 ]; then + echo "h264_encoder_impl_h.patch applied successfully." +else + echo "Failed to apply h264_encoder_impl_h.patch." + exit 1 +fi + +git apply -p1 libwebrtc-patches/build_gn.patch +if [ $? -eq 0 ]; then + echo "build_gn.patch applied successfully." +else + echo "Failed to apply build_gn.patch." + exit 1 +fi diff --git a/examples/basic_room/src/main.rs b/examples/basic_room/src/main.rs index e82b784c2..401815025 100644 --- a/examples/basic_room/src/main.rs +++ b/examples/basic_room/src/main.rs @@ -1,9 +1,30 @@ use livekit::prelude::*; use livekit_api::access_token; use std::env; +use image::GenericImageView; +use std::sync::Arc; +use tokio::time::{sleep, Duration}; +use livekit::options::TrackPublishOptions; +// use livekit_ffi::livekit::proto::track::VideoCodec; +use livekit::options::VideoCodec; +use tokio::signal; +use livekit::webrtc::video_source::RtcVideoSource; +use livekit::webrtc::video_source::VideoResolution; +use livekit::webrtc::{ + native::yuv_helper, + video_frame::{I420Buffer, VideoFrame, VideoRotation}, + video_source::native::NativeVideoSource, +}; +use tokio::sync::Notify; +use std::time::{Instant}; // Connect to a room using the specified env variables // and print all incoming events +// const WIDTH: usize = 1280; +// const HEIGHT: usize = 720; + +const WIDTH: usize = 1440; +const HEIGHT: usize = 1080; #[tokio::main] async fn main() { @@ -18,7 +39,7 @@ async fn main() { .with_name("Rust Bot") .with_grants(access_token::VideoGrants { room_join: true, - room: "my-room".to_string(), + room: "dobby".to_string(), ..Default::default() }) .to_jwt() @@ -27,16 +48,123 @@ async fn main() { let (room, mut rx) = Room::connect(&url, &token, RoomOptions::default()).await.unwrap(); log::info!("Connected to room: {} - {}", room.name(), String::from(room.sid().await)); - room.local_participant() - .publish_data(DataPacket { - payload: "Hello world".to_owned().into_bytes(), - reliable: true, - ..Default::default() - }) - .await - .unwrap(); + // Create a video source and track + let source = NativeVideoSource::new(VideoResolution { + width: WIDTH as u32, + height: HEIGHT as u32, + }); + let track = LocalVideoTrack::create_video_track( + "image", + RtcVideoSource::Native(source.clone()), + ); - while let Some(msg) = rx.recv().await { - log::info!("Event: {:?}", msg); - } + // Publish the track + let options = TrackPublishOptions { + source: TrackSource::Camera, + video_codec: VideoCodec::H264, + ..Default::default() + }; + let publication = room.local_participant().publish_track(LocalTrack::Video(track.clone()), options).await.unwrap(); + println!("Published track with SID: {}", publication.sid()); + + // Start displaying the image + // tokio::spawn(display_image(source)); + // Create a Notify object to signal termination + let notify = Arc::new(Notify::new()); + let notify_clone = notify.clone(); + tokio::spawn(async move { + display_image(source, notify_clone).await; + }); + + // Wait for termination signals + signal::ctrl_c().await.unwrap(); + // room.disconnect().await?; + // Ok(()) } + +async fn display_image(video_source: NativeVideoSource, notify: Arc) { + let frame_duration = Duration::from_millis(33); // Approx. 30 FPS + + // Load the image + let img1 = image::open("/home/integration/test_image1.png").expect("Failed to open image"); + let img1 = img1.resize_exact(WIDTH as u32, HEIGHT as u32, image::imageops::FilterType::Nearest); + let img2 = image::open("/home/integration/test_image2.png").expect("Failed to open image"); + let img2 = img2.resize_exact(WIDTH as u32, HEIGHT as u32, image::imageops::FilterType::Nearest); + + let mut argb_frame = vec![0u8; WIDTH * HEIGHT * 4]; + let mut y_plane = vec![0u8; WIDTH * HEIGHT]; + let mut u_plane = vec![0u8; WIDTH * HEIGHT / 4]; + let mut v_plane = vec![0u8; WIDTH * HEIGHT / 4]; + + let mut last_switch = Instant::now(); + + let mut current_img = &img1; + + loop { + let start_time = tokio::time::Instant::now(); + + tokio::select! { + _ = notify.notified() => { + log::info!("Shutting down display_image loop"); + break; + } + _ = async { + + // Check if 5 seconds have passed + if last_switch.elapsed() >= Duration::from_secs(5) { + // Switch the image + if current_img == &img1 { + current_img = &img2; + } else { + current_img = &img1; + } + // Reset the timer + last_switch = Instant::now(); + } + + // Fill the frame buffer with the image data + for (x, y, pixel) in current_img.pixels() { + let i = (y as usize * WIDTH + x as usize) * 4; + argb_frame[i] = pixel[2]; // B + argb_frame[i + 1] = pixel[1]; // G + argb_frame[i + 2] = pixel[0]; // R + argb_frame[i + 3] = 255; // A + } + + let mut video_frame = VideoFrame { + rotation: VideoRotation::VideoRotation0, + buffer: I420Buffer::new(WIDTH as u32, HEIGHT as u32), + timestamp_us: 0, + }; + + let i420_buffer = &mut video_frame.buffer; + + let (stride_y, stride_u, stride_v) = i420_buffer.strides(); + let (data_y, data_u, data_v) = i420_buffer.data_mut(); + + // Convert ARGB to I420 using abgr_to_i420 + yuv_helper::abgr_to_i420( + &argb_frame, + (WIDTH * 4) as u32, + data_y, + stride_y, + data_u, + stride_u, + data_v, + stride_v, + WIDTH as i32, + HEIGHT as i32, + ); + + video_source.capture_frame(&video_frame); + } => {}, +} + + + // Sleep to maintain the frame rate + let elapsed = start_time.elapsed(); + if frame_duration > elapsed { + sleep(frame_duration - elapsed).await; + } + } +} \ No newline at end of file diff --git a/libwebrtc-hw/.clang-format b/libwebrtc-hw/.clang-format new file mode 100644 index 000000000..1dbcc1a56 --- /dev/null +++ b/libwebrtc-hw/.clang-format @@ -0,0 +1,15 @@ +# The ANYbotics style guide is based on google: https://anybotics.github.io/styleguide/cppguide.html +BasedOnStyle : Google +# Maximum line with is 140 characters (default: 80) +ColumnLimit : 140 +# Indent of two spaces, no tabs. +IndentWidth: 2 +# Always attach braces to surrounding context. +BreakBeforeBraces: Attach +# Force pointer alignement with the type (left). +DerivePointerAlignment: false +PointerAlignment: Left +# Only merge functions defined inside a class. +AllowShortFunctionsOnASingleLine: Inline +# Sort each include block separately. +IncludeBlocks: Preserve diff --git a/libwebrtc-hw/vpl_session.cpp b/libwebrtc-hw/vpl_session.cpp new file mode 100644 index 000000000..4e54e1026 --- /dev/null +++ b/libwebrtc-hw/vpl_session.cpp @@ -0,0 +1,156 @@ +#include "vpl_session.h" + +#include + +#include +#include +#include "va/va.h" +#include "va/va_drm.h" + +namespace { +constexpr char* GPU_RENDER_NODE = "/dev/dri/renderD128"; +} + +namespace any_vpl { + +VplSession::~VplSession() { + MFXClose(session_); + MFXUnload(loader_); +} + +mfxSession VplSession::GetSession() const { + return session_; +} + +bool VplSession::Initialize() { + mfxStatus sts = MFX_ERR_NONE; + + loader_ = MFXLoad(); + if (loader_ == nullptr) { + RTC_LOG(LS_ERROR) << "MFXLoad failed"; + return false; + } + + constexpr char* implementationDescription = "mfxImplDescription.Impl"; + MFX_ADD_PROPERTY_U32(loader_, implementationDescription, MFX_IMPL_TYPE_HARDWARE); + + sts = MFXCreateSession(loader_, 0, &session_); + if (sts != MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "MFXCreateSession failed: sts=" << sts; + return false; + } + + // Query selected implementation + mfxIMPL implementation; + sts = MFXQueryIMPL(session_, &implementation); + if (sts != MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "MFXQueryIMPL failed: sts=" << sts; + return false; + } + + InitAcceleratorHandle(implementation); + + mfxVersion version; + sts = MFXQueryVersion(session_, &version); + if (sts != MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "MFXQueryVersion failed: sts=" << sts; + return false; + } + + RTC_LOG(LS_INFO) << "Intel VPL Implementation: " << (implementation == MFX_IMPL_SOFTWARE ? "SOFTWARE" : "HARDWARE"); + RTC_LOG(LS_INFO) << "Intel VPL Version: " << version.Major << "." << version.Minor; + ShowImplementationInfo(0); + + return true; +} + +void VplSession::ShowImplementationInfo(mfxU32 implnum) { + mfxImplDescription* idesc = nullptr; + mfxStatus sts; + // Loads info about implementation at specified list location + sts = MFXEnumImplementations(loader_, implnum, MFX_IMPLCAPS_IMPLDESCSTRUCTURE, (mfxHDL*)&idesc); + if (!idesc || (sts != MFX_ERR_NONE)) { + return; + } + + RTC_LOG(LS_INFO) << "Implementation details:\n"; + RTC_LOG(LS_INFO) << " ApiVersion: " << idesc->ApiVersion.Major << "." << idesc->ApiVersion.Minor; + RTC_LOG(LS_INFO) << " AccelerationMode via: "; + switch (idesc->AccelerationMode) { + case MFX_ACCEL_MODE_NA: + RTC_LOG(LS_INFO) << "NA"; + break; + case MFX_ACCEL_MODE_VIA_D3D9: + RTC_LOG(LS_INFO) << "D3D9"; + break; + case MFX_ACCEL_MODE_VIA_D3D11: + RTC_LOG(LS_INFO) << "D3D11"; + break; + case MFX_ACCEL_MODE_VIA_VAAPI: + RTC_LOG(LS_INFO) << "VAAPI"; + break; + case MFX_ACCEL_MODE_VIA_VAAPI_DRM_MODESET: + RTC_LOG(LS_INFO) << "VAAPI_DRM_MODESET"; + break; + case MFX_ACCEL_MODE_VIA_VAAPI_GLX: + RTC_LOG(LS_INFO) << "VAAPI_GLX"; + break; + case MFX_ACCEL_MODE_VIA_VAAPI_X11: + RTC_LOG(LS_INFO) << "VAAPI_X11"; + break; + case MFX_ACCEL_MODE_VIA_VAAPI_WAYLAND: + RTC_LOG(LS_INFO) << "VAAPI_WAYLAND"; + break; + case MFX_ACCEL_MODE_VIA_HDDLUNITE: + RTC_LOG(LS_INFO) << "HDDLUNITE"; + break; + default: + RTC_LOG(LS_INFO) << "unknown"; + break; + } + RTC_LOG(LS_INFO) << " DeviceID: " << idesc->Dev.DeviceID; + MFXDispReleaseImplDescription(loader_, idesc); + +#if (MFX_VERSION >= 2004) + // Show implementation path, added in 2.4 API + mfxHDL implPath = nullptr; + sts = MFXEnumImplementations(loader_, implnum, MFX_IMPLCAPS_IMPLPATH, &implPath); + if (!implPath || (sts != MFX_ERR_NONE)) { + return; + } + + RTC_LOG(LS_INFO) << " Path: " << reinterpret_cast(implPath); + MFXDispReleaseImplDescription(loader_, implPath); +#endif +} + +void VplSession::InitAcceleratorHandle(mfxIMPL implementation) { + if ((implementation & MFX_IMPL_VIA_VAAPI) != MFX_IMPL_VIA_VAAPI) { + return; + } + // initialize VAAPI context and set session handle (req in Linux) + accelratorFD_ = open(GPU_RENDER_NODE, O_RDWR); + if (accelratorFD_ < 0) { + RTC_LOG(LS_ERROR) << "Failed to open GPU render node: " << GPU_RENDER_NODE; + return; + } + + vaDisplay_ = vaGetDisplayDRM(accelratorFD_); + if (!vaDisplay_) { + RTC_LOG(LS_ERROR) << "Failed to get VA display from GPU render node: " << GPU_RENDER_NODE; + return; + } + + int majorVersion = 0, minorVersion = 0; + if (VA_STATUS_SUCCESS != vaInitialize(vaDisplay_, &majorVersion, &minorVersion)) { + RTC_LOG(LS_ERROR) << "Failed to initialize VA library"; + return; + } + + RTC_LOG(LS_INFO) << "VAAPI initialized. Version: " << majorVersion << "." << minorVersion; + if (MFXVideoCORE_SetHandle(session_, static_cast(MFX_HANDLE_VA_DISPLAY), vaDisplay_) != MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "Failed to set VA display handle for the VA library to use"; + } +} + +} // namespace any_vpl diff --git a/libwebrtc-hw/vpl_session.h b/libwebrtc-hw/vpl_session.h new file mode 100644 index 000000000..e9981f963 --- /dev/null +++ b/libwebrtc-hw/vpl_session.h @@ -0,0 +1,62 @@ +#ifndef ANY_VPL_SESSION_IMPL_H_ +#define ANY_VPL_SESSION_IMPL_H_ + +#include +#include +#include +#include +#include +#include + +namespace any_vpl { + +/** + * @brief Wraps an Intel® VPL session. + * + */ +class VplSession { + public: + VplSession() = default; + ~VplSession(); + + /** + * @brief Handles all the required initializations for the VPL session. + * MFXLoad, which enumerates and initializes all available runtimes + * MFXCreateSession, which creates a session for the selected runtime + * MFXQueryIMPL, returns the implementation type of a given session + * + */ + bool Initialize(); + + /** + * @brief Get the Vpl Session + * + * @return mfxSession The Vpl Session + */ + mfxSession GetSession() const; + + private: + mfxLoader loader_{nullptr}; + mfxSession session_{nullptr}; + int accelratorFD_{0}; + VADisplay vaDisplay_{nullptr}; + + /** + * @brief If the hardware acceleration goes through the Linux* VA-API infrastructure, this function initializes the VA-API context and + * sets the session handle. + * + * @param implementation The implementation type + */ + void InitAcceleratorHandle(mfxIMPL implementation); + + /** + * @brief Shows implementation info with Intel® VPL + * + * @param implnum + */ + void ShowImplementationInfo(mfxU32 implnum); +}; + +} // namespace any_vpl + +#endif diff --git a/libwebrtc-hw/vpl_utils.cpp b/libwebrtc-hw/vpl_utils.cpp new file mode 100644 index 000000000..2dca7e31b --- /dev/null +++ b/libwebrtc-hw/vpl_utils.cpp @@ -0,0 +1,54 @@ +#include "vpl_utils.h" + +#include +#include +#include +#include + +namespace { + +// std::array to declare as constexpr and avoid clang warnings about exit-time destructor +constexpr std::array, 5> CODEC_STRING_MAP = {{{MFX_CODEC_VP8, "MFX_CODEC_VP8"}, + {MFX_CODEC_VP9, "MFX_CODEC_VP9"}, + {MFX_CODEC_AV1, "MFX_CODEC_AV1"}, + {MFX_CODEC_AVC, "MFX_CODEC_AVC"}, + {MFX_CODEC_HEVC, "MFX_CODEC_HEVC"}}}; + +constexpr std::array, 4> MFX_CODEC_MAP = {{{webrtc::kVideoCodecVP8, MFX_CODEC_VP8}, + {webrtc::kVideoCodecVP9, MFX_CODEC_VP9}, + {webrtc::kVideoCodecAV1, MFX_CODEC_AV1}, + {webrtc::kVideoCodecH264, MFX_CODEC_AVC}}}; + +} // namespace + +namespace any_vpl { + +uint32_t Align16(uint32_t value) { + return (value + 15) & ~15UL; +} + +uint32_t Align32(uint32_t value) { + return (value + 31) & ~31UL; +} + +mfxU32 ToMfxCodec(webrtc::VideoCodecType codec) { + for (const auto& pair : MFX_CODEC_MAP) { + if (pair.first == codec) { + return pair.second; + } + } + + RTC_LOG(LS_ERROR) << __FUNCTION__ << "Unsupported codec: " << codec << " ... Defaulting to AVC"; + return static_cast(MFX_CODEC_AVC); +} + +std::string CodecToString(mfxU32 codec) { + for (const auto& pair : CODEC_STRING_MAP) { + if (pair.first == codec) { + return pair.second; + } + } + return "MFX_CODEC_UNKNOWN"; +} + +} // namespace any_vpl diff --git a/libwebrtc-hw/vpl_utils.h b/libwebrtc-hw/vpl_utils.h new file mode 100644 index 000000000..cf62114d2 --- /dev/null +++ b/libwebrtc-hw/vpl_utils.h @@ -0,0 +1,44 @@ +#ifndef ANY_VPL_UTILS_H_ +#define ANY_VPL_UTILS_H_ + +#include +#include +#include +#include + +namespace any_vpl { + +/** + * @brief Round up to a multiple of 16 + * + * @param value The value to align + * @return uint32_t The aligned value + */ +uint32_t Align16(uint32_t value); + +/** + * @brief Round up to a multiple of 32 + * + * @param value The value to align + * @return uint32_t The aligned value + */ +uint32_t Align32(uint32_t value); + +/** + * @brief Convert a webrtc::VideoCodecType to an mfx type + * + * @param codec The codec to convert + * @return mfxU32 The mfx codec + */ +mfxU32 ToMfxCodec(webrtc::VideoCodecType codec); + +/** + * @brief Convert an mfx codec to a string + * + * @param codec The codec to convert + * @return std::string The string representation of the codec + */ +std::string CodecToString(mfxU32 codec); + +} // namespace any_vpl +#endif diff --git a/libwebrtc-hw/vpl_video_encoder.cpp b/libwebrtc-hw/vpl_video_encoder.cpp new file mode 100644 index 000000000..114a130f6 --- /dev/null +++ b/libwebrtc-hw/vpl_video_encoder.cpp @@ -0,0 +1,512 @@ +#include "vpl_video_encoder.h" + +#include +#include + +// WebRTC +#include +#include +#include +#include +#include + +// Intel VPL +#include +#include +#include +#include +#include +#include "va/va.h" +#include "va/va_drm.h" +// libyuv +#include + +#include "vpl_utils.h" + +namespace { + +constexpr float MIN_ADJUSTED_BITRATE_PERCENTAGE = 0.5; +constexpr float MAX_ADJUSTED_BITRATE_PERCENTAGE = 0.95; + +} // namespace + +namespace any_vpl { + +VplVideoEncoder::VplVideoEncoder(webrtc::VideoCodecType codec) + : codec_(ToMfxCodec(codec)), bitrateAdjuster_(MIN_ADJUSTED_BITRATE_PERCENTAGE, MAX_ADJUSTED_BITRATE_PERCENTAGE) {} + +VplVideoEncoder::~VplVideoEncoder() { + Release(); +} + +mfxStatus VplVideoEncoder::ExecQuery(mfxVideoParam& param) { + mfxVideoParam queryParam = param; + + mfxStatus mfxSts = encoder_->Query(&queryParam, &queryParam); + + if (mfxSts >= 0) { +#define PrintParamInfo(NAME) \ + if (param.NAME != queryParam.NAME) RTC_LOG(LS_WARNING) << "param " << #NAME << " old=" << param.NAME << " new=" << queryParam.NAME + PrintParamInfo(mfx.LowPower); + PrintParamInfo(mfx.BRCParamMultiplier); + PrintParamInfo(mfx.FrameInfo.FrameRateExtN); + PrintParamInfo(mfx.FrameInfo.FrameRateExtD); + PrintParamInfo(mfx.FrameInfo.FourCC); + PrintParamInfo(mfx.FrameInfo.ChromaFormat); + PrintParamInfo(mfx.FrameInfo.PicStruct); + PrintParamInfo(mfx.FrameInfo.CropX); + PrintParamInfo(mfx.FrameInfo.CropY); + PrintParamInfo(mfx.FrameInfo.CropW); + PrintParamInfo(mfx.FrameInfo.CropH); + PrintParamInfo(mfx.FrameInfo.Width); + PrintParamInfo(mfx.FrameInfo.Height); + PrintParamInfo(mfx.CodecId); + PrintParamInfo(mfx.CodecProfile); + PrintParamInfo(mfx.CodecLevel); + PrintParamInfo(mfx.GopPicSize); + PrintParamInfo(mfx.GopRefDist); + PrintParamInfo(mfx.GopOptFlag); + PrintParamInfo(mfx.IdrInterval); + PrintParamInfo(mfx.TargetUsage); + PrintParamInfo(mfx.RateControlMethod); + PrintParamInfo(mfx.InitialDelayInKB); + PrintParamInfo(mfx.TargetKbps); + PrintParamInfo(mfx.MaxKbps); + PrintParamInfo(mfx.BufferSizeInKB); + PrintParamInfo(mfx.NumSlice); + PrintParamInfo(mfx.NumRefFrame); + PrintParamInfo(mfx.EncodedOrder); + PrintParamInfo(mfx.DecodedOrder); + PrintParamInfo(mfx.ExtendedPicStruct); + PrintParamInfo(mfx.TimeStampCalc); + PrintParamInfo(mfx.SliceGroupsPresent); + PrintParamInfo(mfx.MaxDecFrameBuffering); + PrintParamInfo(mfx.EnableReallocRequest); + PrintParamInfo(AsyncDepth); + PrintParamInfo(IOPattern); + + param = queryParam; + } + return mfxSts; +} + +mfxStatus VplVideoEncoder::ExecQueries(mfxVideoParam& param, ExtBuffer& ext) { + mfxStatus mfxSts = MFX_ERR_NONE; + + param = mfxVideoParam(); + + param.mfx.CodecId = codec_; + + // In case we need different configuration instead of the default, we can uncomment below options + if (codec_ == MFX_CODEC_VP8) { + // param.mfx.CodecProfile = MFX_PROFILE_VP8_0; + } else if (codec_ == MFX_CODEC_VP9) { + // param.mfx.CodecProfile = MFX_PROFILE_VP9_0; + } else if (codec_ == MFX_CODEC_AVC) { + // param.mfx.CodecProfile = MFX_PROFILE_AVC_HIGH; + // param.mfx.CodecLevel = MFX_LEVEL_AVC_51; + // param.mfx.CodecProfile = MFX_PROFILE_AVC_MAIN; + // param.mfx.CodecLevel = MFX_LEVEL_AVC_1; + } else if (codec_ == MFX_CODEC_HEVC) { + RTC_LOG(LS_ERROR) << __FUNCTION__ << "Current version of WebRTC used by Livekit doesn't support h265"; + // param.mfx.CodecProfile = MFX_PROFILE_HEVC_MAIN; + // param.mfx.CodecLevel = MFX_LEVEL_HEVC_1; + // param.mfx.LowPower = MFX_CODINGOPTION_OFF; + } else if (codec_ == MFX_CODEC_AV1) { + // param.mfx.CodecProfile = MFX_PROFILE_AV1_MAIN; + } + + param.mfx.TargetUsage = MFX_TARGETUSAGE_BALANCED; + param.mfx.TargetKbps = bitrateAdjuster_.GetAdjustedBitrateBps() / 1000; + param.mfx.MaxKbps = maxBitrateBps_ / 1000; + param.mfx.RateControlMethod = MFX_RATECONTROL_VBR; + param.mfx.FrameInfo.FrameRateExtN = framerate_; + param.mfx.FrameInfo.FrameRateExtD = 1; + param.mfx.FrameInfo.FourCC = MFX_FOURCC_NV12; + param.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV420; + param.mfx.FrameInfo.PicStruct = MFX_PICSTRUCT_PROGRESSIVE; + param.mfx.FrameInfo.CropX = 0; + param.mfx.FrameInfo.CropY = 0; + param.mfx.FrameInfo.CropW = width_; + param.mfx.FrameInfo.CropH = height_; + // Width must be a multiple of 16 + // Height must be a multiple of 16 in case of frame picture and a multiple of + // 32 in case of field picture + param.mfx.FrameInfo.Width = Align16(width_); + param.mfx.FrameInfo.Height = Align16(height_); + + param.mfx.GopRefDist = 1; + param.AsyncDepth = 1; + param.IOPattern = MFX_IOPATTERN_IN_SYSTEM_MEMORY; + + mfxExtBuffer** extBuffers = ext.extBuffers; + mfxExtCodingOption& extCodingOption = ext.extCodingOption; + mfxExtCodingOption2& extCodingOption2 = ext.extCodingOption2; + + int extBuffersSize = 0; + + // In case we need extra configuration, we can uncomment below options + if (codec_ == MFX_CODEC_AVC) { + memset(&extCodingOption, 0, sizeof(extCodingOption)); + extCodingOption.Header.BufferId = MFX_EXTBUFF_CODING_OPTION; + extCodingOption.Header.BufferSz = sizeof(extCodingOption); + extCodingOption.AUDelimiter = MFX_CODINGOPTION_OFF; + extCodingOption.MaxDecFrameBuffering = 1; + // extCodingOption.NalHrdConformance = MFX_CODINGOPTION_OFF; + // extCodingOption.VuiVclHrdParameters = MFX_CODINGOPTION_ON; + // extCodingOption.SingleSeiNalUnit = MFX_CODINGOPTION_ON; + // extCodingOption.RefPicMarkRep = MFX_CODINGOPTION_OFF; + // extCodingOption.PicTimingSEI = MFX_CODINGOPTION_OFF; + // extCodingOption.RecoveryPointSEI = MFX_CODINGOPTION_OFF; + // extCodingOption.FramePicture = MFX_CODINGOPTION_OFF; + // extCodingOption.FieldOutput = MFX_CODINGOPTION_ON; + + memset(&extCodingOption2, 0, sizeof(extCodingOption2)); + extCodingOption2.Header.BufferId = MFX_EXTBUFF_CODING_OPTION2; + extCodingOption2.Header.BufferSz = sizeof(extCodingOption2); + extCodingOption2.RepeatPPS = MFX_CODINGOPTION_ON; + // extCodingOption2.MaxSliceSize = 1; + // extCodingOption2.AdaptiveI = MFX_CODINGOPTION_ON; + + extBuffers[0] = (mfxExtBuffer*)&extCodingOption; + extBuffers[1] = (mfxExtBuffer*)&extCodingOption2; + extBuffersSize = 2; + } else if (codec_ == MFX_CODEC_HEVC) { + memset(&extCodingOption2, 0, sizeof(extCodingOption2)); + extCodingOption2.Header.BufferId = MFX_EXTBUFF_CODING_OPTION2; + extCodingOption2.Header.BufferSz = sizeof(extCodingOption2); + extCodingOption2.RepeatPPS = MFX_CODINGOPTION_ON; + + extBuffers[0] = (mfxExtBuffer*)&extCodingOption2; + extBuffersSize = 1; + } + + if (extBuffersSize != 0) { + param.ExtParam = extBuffers; + param.NumExtParam = extBuffersSize; + } + + mfxSts = ExecQuery(param); + if (mfxSts >= 0) { + return mfxSts; + } + + RTC_LOG(LS_WARNING) << "Unsupported encoder codec: codec=" << CodecToString(codec_) << " mfxSts=" << mfxSts + << " ... Retry with IOPattern IN_SYSTEM_MEMORY only"; + param.IOPattern = MFX_IOPATTERN_IN_SYSTEM_MEMORY; + mfxSts = ExecQuery(param); + if (mfxSts >= 0) { + return mfxSts; + } + + // Turn on LowPower and set H264/H265 to fixed QP mode + RTC_LOG(LS_WARNING) << "Unsupported encoder codec: codec=" << CodecToString(codec_) << " mfxSts=" << mfxSts + << " ... Retry with low power mode"; + param.mfx.LowPower = MFX_CODINGOPTION_ON; + if (codec_ == MFX_CODEC_AVC || codec_ == MFX_CODEC_HEVC) { + param.mfx.RateControlMethod = MFX_RATECONTROL_CQP; + param.mfx.QPI = 25; + param.mfx.QPP = 33; + param.mfx.QPB = 40; + } + mfxSts = ExecQuery(param); + if (mfxSts >= 0) { + return mfxSts; + } + + RTC_LOG(LS_ERROR) << "Unsupported encoder codec: codec=" << CodecToString(codec_) << " mfxSts=" << mfxSts; + + return mfxSts; +} + +int32_t VplVideoEncoder::InitEncode(const webrtc::VideoCodec* codecSettings, int32_t /*numberOfCores*/, size_t /*maxPayloadSize*/) { + RTC_DCHECK(codecSettings); + + session_.reset(); + session_ = std::make_unique(); + if (!session_) { + RTC_LOG(LS_ERROR) << "Failed to create VplSession"; + return WEBRTC_VIDEO_CODEC_ERROR; + } + if (!session_->Initialize()) { + RTC_LOG(LS_ERROR) << "Failed to initialize VplSession"; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + Release(); + + width_ = codecSettings->width; + height_ = codecSettings->height; + targetBitrateBps_ = codecSettings->startBitrate * 1000; + maxBitrateBps_ = codecSettings->maxBitrate * 1000; + bitrateAdjuster_.SetTargetBitrateBps(targetBitrateBps_); + framerate_ = codecSettings->maxFramerate; + mode_ = codecSettings->mode; + + RTC_LOG(LS_INFO) << "InitEncode " << targetBitrateBps_ << "bit/sec"; + + // Initialize encoded image. Default buffer size: size of unencoded data. + encodedImage_._encodedWidth = 0; + encodedImage_._encodedHeight = 0; + encodedImage_.set_size(0); + encodedImage_.timing_.flags = webrtc::VideoSendTiming::TimingFrameFlags::kInvalid; + encodedImage_.content_type_ = (codecSettings->mode == webrtc::VideoCodecMode::kScreensharing) ? webrtc::VideoContentType::SCREENSHARE + : webrtc::VideoContentType::UNSPECIFIED; + + return InitVpl(); +} + +int32_t VplVideoEncoder::RegisterEncodeCompleteCallback(webrtc::EncodedImageCallback* callback) { + std::lock_guard lock(mutex_); + callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} +int32_t VplVideoEncoder::Release() { + return ReleaseVpl(); +} + +int32_t VplVideoEncoder::Encode(const webrtc::VideoFrame& frame, const std::vector* frame_types) { + bool send_key_frame = false; + + if (frame_types != nullptr) { + // We only support a single stream. + RTC_DCHECK_EQ(frame_types->size(), static_cast(1)); + // Skip frame? + if ((*frame_types)[0] == webrtc::VideoFrameType::kEmptyFrame) { + return WEBRTC_VIDEO_CODEC_OK; + } + // Force key frame? + send_key_frame = (*frame_types)[0] == webrtc::VideoFrameType::kVideoFrameKey; + } + + // Remove unused input surfaces + auto surface = std::find_if(surfaces_.begin(), surfaces_.end(), [](const mfxFrameSurface1& s) { return !s.Data.Locked; }); + if (surface == surfaces_.end()) { + RTC_LOG(LS_ERROR) << "Surface not found"; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + // Convert I420 to NV12 + rtc::scoped_refptr frame_buffer = frame.video_frame_buffer()->ToI420(); + libyuv::I420ToNV12(frame_buffer->DataY(), frame_buffer->StrideY(), frame_buffer->DataU(), frame_buffer->StrideU(), frame_buffer->DataV(), + frame_buffer->StrideV(), surface->Data.Y, surface->Data.Pitch, surface->Data.U, surface->Data.Pitch, + frame_buffer->width(), frame_buffer->height()); + + mfxStatus mfxSts; + + mfxEncodeCtrl ctrl; + memset(&ctrl, 0, sizeof(ctrl)); + if (send_key_frame) { + ctrl.FrameType = MFX_FRAMETYPE_I | MFX_FRAMETYPE_IDR | MFX_FRAMETYPE_REF; + } else { + ctrl.FrameType = MFX_FRAMETYPE_UNKNOWN; + } + + if (reconfigureNeeded_) { + const auto start_time = std::chrono::system_clock::now(); + RTC_LOG(LS_INFO) << "Start reconfigure: bps=" << (bitrateAdjuster_.GetAdjustedBitrateBps() / 1000) << " framerate=" << framerate_; + + mfxVideoParam param; + memset(¶m, 0, sizeof(param)); + mfxSts = encoder_->GetVideoParam(¶m); + if (mfxSts < MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "GetVideoParam failed: mfxSts=" << mfxSts; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + param.mfx.TargetKbps = bitrateAdjuster_.GetAdjustedBitrateBps() / 1000; + param.mfx.FrameInfo.FrameRateExtN = framerate_; + + mfxSts = encoder_->Reset(¶m); + if (mfxSts < MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "Encoder Reset failed: mfxSts=" << mfxSts; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + reconfigureNeeded_ = false; + + const auto end_time = std::chrono::system_clock::now(); + RTC_LOG(LS_INFO) << "Finish reconfigure: " << std::chrono::duration_cast(end_time - start_time).count() + << " ms"; + } + + mfxSyncPoint syncp; + mfxSts = encoder_->EncodeFrameAsync(&ctrl, &*surface, &bitstream_, &syncp); + if (mfxSts == MFX_ERR_MORE_DATA) { + // More input needed, try again + return WEBRTC_VIDEO_CODEC_OK; + } + if (mfxSts < MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "EncodeFrameAsync failed: mfxSts=" << mfxSts; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + static constexpr mfxU32 waitMs = 300000; + mfxSts = MFXVideoCORE_SyncOperation(session_->GetSession(), syncp, waitMs); + if (mfxSts < MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "MFXVideoCORE_SyncOperation failed: mfxSts=" << mfxSts; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + uint8_t* p = bitstream_.Data + bitstream_.DataOffset; + int size = bitstream_.DataLength; + bitstream_.DataLength = 0; + + auto buf = webrtc::EncodedImageBuffer::Create(p, size); + encodedImage_.SetEncodedData(buf); + encodedImage_._encodedWidth = width_; + encodedImage_._encodedHeight = height_; + encodedImage_.content_type_ = + (mode_ == webrtc::VideoCodecMode::kScreensharing) ? webrtc::VideoContentType::SCREENSHARE : webrtc::VideoContentType::UNSPECIFIED; + encodedImage_.timing_.flags = webrtc::VideoSendTiming::kInvalid; + encodedImage_.SetTimestamp(frame.timestamp()); + encodedImage_.ntp_time_ms_ = frame.ntp_time_ms(); + encodedImage_.capture_time_ms_ = frame.render_time_ms(); + encodedImage_.rotation_ = frame.rotation(); + encodedImage_.SetColorSpace(frame.color_space()); + keyFrameInterval_ += 1; + if (bitstream_.FrameType & MFX_FRAMETYPE_I || bitstream_.FrameType & MFX_FRAMETYPE_IDR) { + encodedImage_._frameType = webrtc::VideoFrameType::kVideoFrameKey; + RTC_LOG(LS_INFO) << "Key Frame Generated: key_frame_interval=" << keyFrameInterval_; + keyFrameInterval_ = 0; + } else { + encodedImage_._frameType = webrtc::VideoFrameType::kVideoFrameDelta; + } + + webrtc::CodecSpecificInfo codecSpecific; + if (codec_ == MFX_CODEC_AVC) { + codecSpecific.codecType = webrtc::kVideoCodecH264; + codecSpecific.codecSpecific.H264.packetization_mode = webrtc::H264PacketizationMode::NonInterleaved; + + h264BitstreamParser_.ParseBitstream(encodedImage_); + encodedImage_.qp_ = h264BitstreamParser_.GetLastSliceQp().value_or(-1); + } + + std::unique_lock lock(mutex_); + webrtc::EncodedImageCallback::Result result = callback_->OnEncodedImage(encodedImage_, &codecSpecific); + lock.unlock(); + + if (result.error != webrtc::EncodedImageCallback::Result::OK) { + RTC_LOG(LS_ERROR) << __FUNCTION__ << " OnEncodedImage failed error:" << result.error; + return WEBRTC_VIDEO_CODEC_ERROR; + } + bitrateAdjuster_.Update(size); + + return WEBRTC_VIDEO_CODEC_OK; +} + +void VplVideoEncoder::SetRates(const RateControlParameters& parameters) { + if (parameters.framerate_fps < 1.0) { + RTC_LOG(LS_WARNING) << "Invalid frame rate: " << parameters.framerate_fps; + return; + } + + uint32_t newFramerate = (uint32_t)parameters.framerate_fps; + uint32_t newBitrate = parameters.bitrate.get_sum_bps(); + RTC_LOG(LS_INFO) << __FUNCTION__ << " framerate_:" << framerate_ << " newFramerate: " << newFramerate + << " targetBitrateBps_:" << targetBitrateBps_ << " newBitrate:" << newBitrate << " maxBitrateBps_:" << maxBitrateBps_; + framerate_ = newFramerate; + targetBitrateBps_ = newBitrate; + bitrateAdjuster_.SetTargetBitrateBps(targetBitrateBps_); + reconfigureNeeded_ = true; +} + +webrtc::VideoEncoder::EncoderInfo VplVideoEncoder::GetEncoderInfo() const { + webrtc::VideoEncoder::EncoderInfo info; + info.supports_native_handle = true; + info.implementation_name = "libvpl"; + info.scaling_settings = VideoEncoder::ScalingSettings::kOff; + info.is_hardware_accelerated = true; + return info; +} + +int32_t VplVideoEncoder::InitVpl() { + encoder_ = std::make_unique(session_->GetSession()); + + mfxPlatform platform; + memset(&platform, 0, sizeof(platform)); + MFXVideoCORE_QueryPlatform(session_->GetSession(), &platform); + RTC_LOG(LS_INFO) << "Codec=" << CodecToString(codec_) << " CodeName=" << platform.CodeName << " DeviceId=" << platform.DeviceId + << " MediaAdapterType=" << platform.MediaAdapterType; + + mfxVideoParam param; + // ExtBuffer needs to be declared here so that it is not outlived by the param, by the end of this function + ExtBuffer extendedBufferOpts; + mfxStatus mfxSts = ExecQueries(param, extendedBufferOpts); + if (mfxSts < MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "Failed ExecQueries: mfxSts=" << mfxSts; + return WEBRTC_VIDEO_CODEC_ERROR; + } + if (mfxSts > MFX_ERR_NONE) { + RTC_LOG(LS_WARNING) << "Supported specified codec but has warning: codec=" << CodecToString(codec_) << " mfxSts=" << mfxSts; + } + + mfxSts = encoder_->Init(¶m); + if (mfxSts < MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "Failed to Init encoder: mfxSts=" << mfxSts; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + mfxSts = MFX_ERR_NONE; + memset(¶m, 0, sizeof(param)); + + // Retrieve video parameters selected by encoder. + // - BufferSizeInKB parameter is required to set bit stream buffer size + mfxSts = encoder_->GetVideoParam(¶m); + if (mfxSts < MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "Failed to GetVideoParam: mfxSts=" << mfxSts; + return WEBRTC_VIDEO_CODEC_ERROR; + } + RTC_LOG(LS_INFO) << "BufferSizeInKB=" << param.mfx.BufferSizeInKB; + + // Query number of required surfaces for encoder + memset(&allocRequest_, 0, sizeof(allocRequest_)); + mfxSts = encoder_->QueryIOSurf(¶m, &allocRequest_); + if (mfxSts < MFX_ERR_NONE) { + RTC_LOG(LS_ERROR) << "Failed to QueryIOSurf: mfxSts=" << mfxSts; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + RTC_LOG(LS_INFO) << "Encoder NumFrameSuggested=" << allocRequest_.NumFrameSuggested; + + frameInfo_ = param.mfx.FrameInfo; + + // Initializing the Output Bitstream + bitstreamBuffer_.resize(param.mfx.BufferSizeInKB * 1000); + + memset(&bitstream_, 0, sizeof(bitstream_)); + bitstream_.MaxLength = bitstreamBuffer_.size(); + bitstream_.Data = bitstreamBuffer_.data(); + + const int width = Align32(allocRequest_.Info.Width); + const int height = Align32(allocRequest_.Info.Height); + // Number of bytes per page + // NV12 => 12 bits per pixel + const int size = width * height * 12 / 8; + surfaceBuffer_.resize(allocRequest_.NumFrameSuggested * size); + + surfaces_.clear(); + surfaces_.reserve(allocRequest_.NumFrameSuggested); + for (int i = 0; i < allocRequest_.NumFrameSuggested; i++) { + mfxFrameSurface1 surface; + memset(&surface, 0, sizeof(surface)); + surface.Info = frameInfo_; + surface.Data.Y = surfaceBuffer_.data() + i * size; + surface.Data.U = surfaceBuffer_.data() + i * size + width * height; + surface.Data.V = surfaceBuffer_.data() + i * size + width * height + 1; + surface.Data.Pitch = width; + surfaces_.push_back(surface); + } + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t VplVideoEncoder::ReleaseVpl() { + if (encoder_ != nullptr) { + encoder_->Close(); + } + encoder_.reset(); + return WEBRTC_VIDEO_CODEC_OK; +} + +} // namespace any_vpl diff --git a/libwebrtc-hw/vpl_video_encoder.h b/libwebrtc-hw/vpl_video_encoder.h new file mode 100644 index 000000000..b30e85b60 --- /dev/null +++ b/libwebrtc-hw/vpl_video_encoder.h @@ -0,0 +1,108 @@ +#ifndef ANY_VPL_VIDEO_ENCODER_H_ +#define ANY_VPL_VIDEO_ENCODER_H_ + +#include + +// WebRTC +#include +#include +#include +#include +#include "vpl_session.h" + +namespace any_vpl { + +/** + * @brief A class that implements an accelerated video encoder using Intel® VPL. + * + */ +class VplVideoEncoder : public webrtc::VideoEncoder { + public: + /** + * @brief Construct a new Vpl Video Encoder object + * + * @param session A Vpl session + * @param codec The codec to use + */ + VplVideoEncoder(webrtc::VideoCodecType codec); + virtual ~VplVideoEncoder() override; + + // webrtc::VideoEncoder overrides + int32_t InitEncode(const webrtc::VideoCodec* codecSettings, int32_t numberOfCores, size_t maxPayloadSize) override; + int32_t RegisterEncodeCompleteCallback(webrtc::EncodedImageCallback* callback) override; + int32_t Release() override; + int32_t Encode(const webrtc::VideoFrame& frame, const std::vector* frameTypes) override; + void SetRates(const RateControlParameters& parameters) override; + webrtc::VideoEncoder::EncoderInfo GetEncoderInfo() const override; + + private: + /** + * @brief Struct that aggregates variables for setting additional options for encoding + * + */ + struct ExtBuffer { + mfxExtBuffer* extBuffers[2]; + mfxExtCodingOption extCodingOption; + mfxExtCodingOption2 extCodingOption2; + }; + + std::mutex mutex_; + webrtc::EncodedImageCallback* callback_ = nullptr; + + uint32_t targetBitrateBps_ = 0; + uint32_t maxBitrateBps_ = 0; + bool reconfigureNeeded_ = false; + uint32_t width_ = 0; + uint32_t height_ = 0; + uint32_t framerate_ = 0; + webrtc::VideoCodecMode mode_ = webrtc::VideoCodecMode::kRealtimeVideo; + webrtc::EncodedImage encodedImage_; + webrtc::H264BitstreamParser h264BitstreamParser_; + + std::vector surfaceBuffer_; + std::vector surfaces_; + + std::unique_ptr session_; + mfxU32 codec_; + webrtc::BitrateAdjuster bitrateAdjuster_; + mfxFrameAllocRequest allocRequest_; + std::unique_ptr encoder_; + std::vector bitstreamBuffer_; + mfxBitstream bitstream_; + mfxFrameInfo frameInfo_; + + int keyFrameInterval_ = 0; + + /** + * @brief Helper method to execute a query + * + * @param param Configuration parameters for encoding + * @return mfxStatus MFX_ERR_NONE if successful, otherwise an error/warning code + */ + mfxStatus ExecQuery(mfxVideoParam& param); + + /** + * @brief Tries queries in various patterns and returns the param when successful + * + * @param param Configuration parameters for encoding + * @param ext Additional options for encoding + * @return mfxStatus MFX_ERR_NONE if successful, otherwise an error/warning code + */ + mfxStatus ExecQueries(mfxVideoParam& param, ExtBuffer& ext); + + /** + * @brief Initialize VPL context + * + * @return int32_t WEBRTC_VIDEO_CODEC_OK if successful, otherwise an error code + */ + int32_t InitVpl(); + + /** + * Close VPL context and release resources + */ + int32_t ReleaseVpl(); +}; + +} // namespace any_vpl + +#endif diff --git a/libwebrtc-patches/build_gn.patch b/libwebrtc-patches/build_gn.patch new file mode 100644 index 000000000..92b7a33ac --- /dev/null +++ b/libwebrtc-patches/build_gn.patch @@ -0,0 +1,25 @@ +diff --git org/webrtc-sys/libwebrtc/src/modules/video_coding/BUILD.gn update/webrtc-sys/libwebrtc/src/modules/video_coding/BUILD.gn +index 7cb713b..12ba096 100644 +--- org/webrtc-sys/libwebrtc/src/modules/video_coding/BUILD.gn ++++ update/webrtc-sys/libwebrtc/src/modules/video_coding/BUILD.gn +@@ -466,9 +466,19 @@ rtc_library("webrtc_h264") { + "codecs/h264/h264_encoder_impl.cc", + "codecs/h264/h264_encoder_impl.h", + "codecs/h264/include/h264.h", ++ "../../../../../libwebrtc-hw/vpl_video_encoder.h", ++ "../../../../../libwebrtc-hw/vpl_video_encoder.cpp", ++ "../../../../../libwebrtc-hw/vpl_utils.h", ++ "../../../../../libwebrtc-hw/vpl_utils.cpp", ++ "../../../../../libwebrtc-hw/vpl_session.h", ++ "../../../../../libwebrtc-hw/vpl_session.cpp" ++ ] ++ ++ include_dirs = [ ++ "../../../../../", # Include the hw encoder implementation ++ "/usr/local/vplinstall/include/vpl" # Include the headers from libvpl + ] + +- defines = [] + deps = [ + ":video_codec_interface", + ":video_coding_utility", diff --git a/libwebrtc-patches/h264_encoder_impl_cc.patch b/libwebrtc-patches/h264_encoder_impl_cc.patch new file mode 100644 index 000000000..3e76f2917 --- /dev/null +++ b/libwebrtc-patches/h264_encoder_impl_cc.patch @@ -0,0 +1,118 @@ +diff --git org/webrtc-sys/libwebrtc/src/modules/video_coding/codecs/h264/h264_encoder_impl.cc update/webrtc-sys/libwebrtc/src/modules/video_coding/codecs/h264/h264_encoder_impl.cc +index b6023ac..176b166 100644 +--- org/webrtc-sys/libwebrtc/src/modules/video_coding/codecs/h264/h264_encoder_impl.cc ++++ update/webrtc-sys/libwebrtc/src/modules/video_coding/codecs/h264/h264_encoder_impl.cc +@@ -12,6 +12,7 @@ + // Everything declared/defined in this header is only required when WebRTC is + // build with H264 support, please do not move anything out of the + // #ifdef unless needed and tested. ++ + #ifdef WEBRTC_USE_H264 + + #include "modules/video_coding/codecs/h264/h264_encoder_impl.h" +@@ -171,7 +172,8 @@ static void RtpFragmentize(EncodedImage* encoded_image, SFrameBSInfo* info) { + } + + H264EncoderImpl::H264EncoderImpl(const cricket::VideoCodec& codec) +- : packetization_mode_(H264PacketizationMode::SingleNalUnit), ++ : useHWEncoder_(false), ++ packetization_mode_(H264PacketizationMode::SingleNalUnit), + max_payload_size_(0), + number_of_cores_(0), + encoded_image_callback_(nullptr), +@@ -190,14 +192,42 @@ H264EncoderImpl::H264EncoderImpl(const cricket::VideoCodec& codec) + configurations_.reserve(kMaxSimulcastStreams); + tl0sync_limit_.reserve(kMaxSimulcastStreams); + svc_controllers_.reserve(kMaxSimulcastStreams); ++ ++ // Use the following lines to enable logging for debugging ++ // rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE); ++ // rtc::LogMessage::SetLogToStderr(true); ++ ++ vplVideoEncoder_ = ++ std::make_unique(kVideoCodecH264); ++ if (!vplVideoEncoder_) { ++ useHWEncoder_ = false; ++ RTC_LOG(LS_ERROR) << "Failed to create VplVideoEncoder"; ++ return; ++ } + } + + H264EncoderImpl::~H264EncoderImpl() { ++ if (useHWEncoder_) { ++ vplVideoEncoder_->Release(); ++ return; ++ } ++ + Release(); + } + + int32_t H264EncoderImpl::InitEncode(const VideoCodec* inst, + const VideoEncoder::Settings& settings) { ++ const int32_t retVal = vplVideoEncoder_->InitEncode( ++ inst, settings.number_of_cores, settings.max_payload_size); ++ useHWEncoder_ = (retVal == WEBRTC_VIDEO_CODEC_OK); ++ ++ if (useHWEncoder_) { ++ return retVal; ++ } ++ ++ RTC_LOG(LS_WARNING) ++ << "Failed to initialize VplVideoEncoder. Using software encoder."; ++ + ReportInit(); + if (!inst || inst->codecType != kVideoCodecH264) { + ReportError(); +@@ -339,6 +369,10 @@ int32_t H264EncoderImpl::InitEncode(const VideoCodec* inst, + } + + int32_t H264EncoderImpl::Release() { ++ if (useHWEncoder_) { ++ return vplVideoEncoder_->Release(); ++ } ++ + while (!encoders_.empty()) { + ISVCEncoder* openh264_encoder = encoders_.back(); + if (openh264_encoder) { +@@ -359,11 +393,19 @@ int32_t H264EncoderImpl::Release() { + + int32_t H264EncoderImpl::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { ++ if (useHWEncoder_) { ++ return vplVideoEncoder_->RegisterEncodeCompleteCallback(callback); ++ } ++ + encoded_image_callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; + } + + void H264EncoderImpl::SetRates(const RateControlParameters& parameters) { ++ if (useHWEncoder_) { ++ return vplVideoEncoder_->SetRates(parameters); ++ } ++ + if (encoders_.empty()) { + RTC_LOG(LS_WARNING) << "SetRates() while uninitialized."; + return; +@@ -411,6 +453,10 @@ void H264EncoderImpl::SetRates(const RateControlParameters& parameters) { + int32_t H264EncoderImpl::Encode( + const VideoFrame& input_frame, + const std::vector* frame_types) { ++ if (useHWEncoder_) { ++ return vplVideoEncoder_->Encode(input_frame, frame_types); ++ } ++ + if (encoders_.empty()) { + ReportError(); + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; +@@ -702,6 +748,10 @@ void H264EncoderImpl::ReportError() { + } + + VideoEncoder::EncoderInfo H264EncoderImpl::GetEncoderInfo() const { ++ if (useHWEncoder_) { ++ return vplVideoEncoder_->GetEncoderInfo(); ++ } ++ + EncoderInfo info; + info.supports_native_handle = false; + info.implementation_name = "OpenH264"; diff --git a/libwebrtc-patches/h264_encoder_impl_h.patch b/libwebrtc-patches/h264_encoder_impl_h.patch new file mode 100644 index 000000000..73821550d --- /dev/null +++ b/libwebrtc-patches/h264_encoder_impl_h.patch @@ -0,0 +1,22 @@ +diff --git org/webrtc-sys/libwebrtc/src/modules/video_coding/codecs/h264/h264_encoder_impl.h update/webrtc-sys/libwebrtc/src/modules/video_coding/codecs/h264/h264_encoder_impl.h +index 19c16f3..af0424b 100644 +--- org/webrtc-sys/libwebrtc/src/modules/video_coding/codecs/h264/h264_encoder_impl.h ++++ update/webrtc-sys/libwebrtc/src/modules/video_coding/codecs/h264/h264_encoder_impl.h +@@ -31,6 +31,7 @@ + #include "api/video_codecs/scalability_mode.h" + #include "api/video_codecs/video_encoder.h" + #include "common_video/h264/h264_bitstream_parser.h" ++#include "libwebrtc-hw/vpl_video_encoder.h" + #include "modules/video_coding/codecs/h264/include/h264.h" + #include "modules/video_coding/svc/scalable_video_controller.h" + #include "modules/video_coding/utility/quality_scaler.h" +@@ -90,6 +91,9 @@ class H264EncoderImpl : public H264Encoder { + } + + private: ++ std::unique_ptr vplVideoEncoder_; ++ bool useHWEncoder_; ++ + SEncParamExt CreateEncoderParams(size_t i) const; + + webrtc::H264BitstreamParser h264_bitstream_parser_; diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index 69695330c..c0b1519ce 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -132,6 +132,11 @@ fn main() { println!("cargo:rustc-link-lib=dylib=dl"); println!("cargo:rustc-link-lib=dylib=pthread"); println!("cargo:rustc-link-lib=dylib=m"); + println!("cargo:rustc-link-lib=dylib=va"); + println!("cargo:rustc-link-lib=dylib=va-drm"); + + println!("cargo:rustc-link-search=/usr/local/vplinstall/lib/"); + println!("cargo:rustc-link-lib=dylib=vpl"); builder.flag("-std=c++2a"); } diff --git a/webrtc-sys/libwebrtc/build_linux.sh b/webrtc-sys/libwebrtc/build_linux.sh index 5f1af2212..ee2bc1fc7 100755 --- a/webrtc-sys/libwebrtc/build_linux.sh +++ b/webrtc-sys/libwebrtc/build_linux.sh @@ -103,8 +103,13 @@ args="is_debug=$debug \ use_rtti=true \ rtc_use_x11=false" +release_debug_symbols="false" + if [ "$debug" = "true" ]; then args="${args} is_asan=true is_lsan=true"; +elif [ "$release_debug_symbols" = "true" ]; then + # Only for profiling purposes + args="${args} is_debug=false symbol_level=2" fi # generate ninja files @@ -126,5 +131,5 @@ cp "$OUTPUT_DIR/args.gn" "$ARTIFACTS_DIR" cp "$OUTPUT_DIR/LICENSE.md" "$ARTIFACTS_DIR" cd src -find . -name "*.h" -print | cpio -pd "$ARTIFACTS_DIR/include" +find . -name "*.h" -print | cpio -pd "$ARTIFACTS_DIR/include" > /dev/null 2>&1